This file is used to analyse the ORS + IFE basal dataset.

library(dplyr)
library(patchwork)
library(ggplot2)
library(ComplexHeatmap)

.libPaths()
## [1] "/usr/local/lib/R/library"

Preparation

In this section, we set the global settings of the analysis. We will store data there :

save_name = "ors_ifeb"
out_dir = "."

We load the dataset :

sobj = readRDS(paste0(out_dir, "/", save_name, "_sobj.rds"))
sobj
## An object of class Seurat 
## 16683 features across 3416 samples within 1 assay 
## Active assay: RNA (16683 features, 2000 variable features)
##  6 dimensional reductions calculated: RNA_pca, RNA_pca_20_tsne, RNA_pca_20_umap, harmony, harmony_20_umap, harmony_20_tsne

We load the sample information :

sample_info = readRDS(paste0(out_dir, "/../../1_metadata/hs_hd_sample_info.rds"))
project_names_oi = sample_info$project_name

graphics::pie(rep(1, nrow(sample_info)),
              col = sample_info$color,
              labels = sample_info$project_name)

Here are custom colors for each cell type :

color_markers = readRDS(paste0(out_dir, "/../../1_metadata/hs_hd_color_markers.rds"))

data.frame(cell_type = names(color_markers),
           color = unlist(color_markers)) %>%
  ggplot2::ggplot(., aes(x = cell_type, y = 0, fill = cell_type)) +
  ggplot2::geom_point(pch = 21, size = 5) +
  ggplot2::scale_fill_manual(values = unlist(color_markers), breaks = names(color_markers)) +
  ggplot2::theme_classic() +
  ggplot2::theme(legend.position = "none",
                 axis.line = element_blank(),
                 axis.title = element_blank(),
                 axis.ticks = element_blank(),
                 axis.text.y = element_blank(),
                 axis.text.x = element_text(angle = 30, hjust = 1))

This is the projection of interest :

name2D = "harmony_20_tsne"

We design a custom function to make the GSEA plot and a word cloud graph :

make_gsea_plot = function(gsea_results, gs_oi, fold_change, metric = "FC") {
  fold_change$metric = fold_change[, metric]
  
  plot_list = lapply(gs_oi, FUN = function(gene_set) {
    # Gene set content
    gs_content = gene_sets %>%
      dplyr::filter(gs_name == gene_set) %>%
      dplyr::pull(ensembl_gene) %>%
      unique()
    
    # Gene set size
    nb_genes = length(gs_content)
    
    # Enrichment metrics
    NES = gsea_results@result[gene_set, "NES"]
    p.adjust = gsea_results@result[gene_set, "p.adjust"] %>%
      round(., 4)
    qvalues = gsea_results@result[gene_set, "qvalues"]
    
    if (p.adjust > 0.05) {
      p.adjust = paste0("<span style='color:red;'>", p.adjust, "</span>")
    }
    
    my_subtitle = paste0("\nNES : ", round(NES, 2),
                         " | padj : ", p.adjust,
                         " | qval : ", round(qvalues, 4),
                         " | set size : ", nb_genes, " genes")
    
    # Size limits
    lower_FC = min(fold_change[gs_content, ]$metric, na.rm = TRUE)
    upper_FC = max(fold_change[gs_content, ]$metric, na.rm = TRUE)
    
    # Plot
    p = enrichplot::gseaplot2(x = gsea_results, geneSetID = gene_set) +
      ggplot2::labs(title = gene_set,
                    subtitle = my_subtitle) +
      ggplot2::theme(plot.title = element_text(hjust = 0.5, face = "bold",
                                               margin = ggplot2::margin(3, 3, 5, 3)),
                     plot.subtitle = ggtext::element_markdown(hjust = 0.5,
                                                              size = 10))
    
    wc = ggplot2::ggplot(fold_change[gs_content, ],
                         aes(label = gene_name, size = abs(metric), color = metric)) +
      ggwordcloud::geom_text_wordcloud_area(show.legend = TRUE) +
      ggplot2::scale_color_gradient2(
        name = metric,
        low = aquarius::color_cnv[1],
        mid = "gray70", midpoint = 0,
        high = aquarius::color_cnv[3]) +
      ggplot2::scale_size_area(max_size = 7) +
      ggplot2::theme_minimal() +
      ggplot2::guides(size = "none")
    
    return(list(p, wc))
  }) %>% unlist(., recursive = FALSE)
  
  return(plot_list)
}

Visualization

Gene expression

We visualize gene expression for some markers :

features = c("percent.mt", "percent.rb", "nFeature_RNA")

plot_list = lapply(features, FUN = function(one_gene) {
  Seurat::FeaturePlot(sobj, features = one_gene,
                      reduction = name2D) +
    ggplot2::theme(aspect.ratio = 1) +
    ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
    Seurat::NoAxes()
})

patchwork::wrap_plots(plot_list, ncol = 3)

Clusters

We visualize clusters :

cluster_plot = Seurat::DimPlot(sobj, reduction = name2D, label = TRUE) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1)
cluster_plot

Cell type

We visualize cell type split by sample :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        group_by = "cell_type",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_color = color_markers,
                                        bg_pt_size = 0.5, main_pt_size = 0.5)

plot_list[[length(plot_list) + 1]] = cluster_plot

patchwork::wrap_plots(plot_list, ncol = 4) &
  Seurat::NoLegend()

Cluster type

We summarize major cell type by cluster :

cell_type_clusters = sobj@meta.data[, c("cell_type", "seurat_clusters")] %>%
  table() %>%
  prop.table(., margin = 2) %>%
  apply(., 2, which.max)
cell_type_clusters = setNames(levels(sobj$cell_type)[cell_type_clusters],
                              nm = names(cell_type_clusters))

We define cluster type :

sobj$cluster_type = cell_type_clusters[sobj$seurat_clusters] %>%
  as.factor()
table(sobj$cluster_type, sobj$cell_type)
##            
##             CD4 T cells CD8 T cells Langerhans cells macrophages B cells
##   IFE basal           0           0                0           0       1
##   ORS                 0           0                0           0       0
##            
##             cuticle cortex medulla  IRS proliferative HF-SCs IFE basal
##   IFE basal       1      0       4    1            16     41      1745
##   ORS             2      0       1    0             8     17         4
##            
##             IFE granular spinous  ORS sebocytes
##   IFE basal                   60   29        16
##   ORS                         12 1454         4

We subset color_markers :

color_markers = color_markers[levels(sobj$cluster_type)]

We compare cluster annotation and cell type annotation :

p1 = Seurat::DimPlot(sobj, group.by = "cell_type",
                     reduction = name2D, cols = color_markers) +
  ggplot2::labs(title = "Cell type") +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

p2 = Seurat::DimPlot(sobj, group.by = "cluster_type",
                     reduction = name2D, cols = color_markers) +
  ggplot2::labs(title = "Cluster type") +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

patchwork::wrap_plots(p1, p2, guides = "collect")

Barplot

We make a barplot to compare ORS and IFE basal populations in HS vs HD samples. The proportion of ORS is indicated on top of bars.

quantif = table(sobj$sample_identifier,
                sobj$cluster_type) %>%
  as.data.frame.table() %>%
  `colnames<-`(c("Sample", "cell_type", "nb_cells")) %>%
  dplyr::group_by(Sample) %>%
  dplyr::mutate(total_cells = sum(nb_cells)) %>%
  as.data.frame() %>%
  dplyr::filter(cell_type == "ORS") %>%
  dplyr::mutate(prop_ibl = nb_cells / total_cells) %>%
  dplyr::mutate(prop_ibl = 100*round(prop_ibl, 4))

sobj$seurat_clusters = factor(sobj$seurat_clusters,
                              levels = names(sort(cell_type_clusters)))

aquarius::plot_barplot(df = table(sobj$sample_identifier,
                                  sobj$seurat_clusters) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("sample_identifier", "clusters", "nb_cells")),
                       x = "sample_identifier", y = "nb_cells", fill = "clusters",
                       position = position_fill()) +
  ggplot2::scale_fill_manual(values = c(colorRampPalette(c("royalblue1", "royalblue4"))(table(sort(cell_type_clusters))["IFE basal"]),
                                        colorRampPalette(c("chartreuse1", "chartreuse4"))(table(sort(cell_type_clusters))["ORS"])),
                             breaks = names(sort(cell_type_clusters)),
                             name = "Cell type") +
  ggplot2::geom_label(data = quantif, inherit.aes = FALSE,
                      aes(x = .data$Sample, y = 1.05, label = .data$prop_ibl),
                      label.size = 0, size = 5)

Differential expression

In this section, we perform DE between :

  • inner bulge layer (ORS) and outer root sheath (IFE basal) populations
  • healthy donors (HD) and HS patients (HS) within IFE basal population
  • healthy donors (HD) and HS patients (HS) within ORS population
  • cluster 4 vs rest of IFE basal, because this cluster is specific to HS compared to HD

We save the results in a list :

list_results = list()

We make over-representation analysis for each group of genes. We load gene sets from MSigDB :

gene_sets = aquarius::get_gene_sets(species = "Homo sapiens")
gene_sets = gene_sets$gene_sets

head(gene_sets)
## # A tibble: 6 x 16
##   gs_cat gs_subcat gs_name gene_symbol entrez_gene ensembl_gene human_gene_symb~
##   <chr>  <chr>     <chr>   <chr>             <int> <chr>        <chr>           
## 1 C5     GO:BP     GOBP_1~ AASDHPPT          60496 ENSG0000014~ AASDHPPT        
## 2 C5     GO:BP     GOBP_1~ ALDH1L1           10840 ENSG0000014~ ALDH1L1         
## 3 C5     GO:BP     GOBP_1~ ALDH1L2          160428 ENSG0000013~ ALDH1L2         
## 4 C5     GO:BP     GOBP_1~ MTHFD1             4522 ENSG0000010~ MTHFD1          
## 5 C5     GO:BP     GOBP_1~ MTHFD1L           25902 ENSG0000012~ MTHFD1L         
## 6 C5     GO:BP     GOBP_1~ MTHFD2L          441024 ENSG0000016~ MTHFD2L         
## # ... with 9 more variables: human_entrez_gene <int>, human_ensembl_gene <chr>,
## #   gs_id <chr>, gs_pmid <chr>, gs_geoid <chr>, gs_exact_source <chr>,
## #   gs_url <chr>, gs_description <chr>, category <chr>

How many gene sets ?

gene_sets[, c("gs_subcat", "gs_name")] %>%
  dplyr::distinct() %>%
  dplyr::pull(gs_subcat) %>%
  table() %>%
  as.data.frame.table() %>%
  `colnames<-`(c("Category", "Nb gene sets"))
##          Category Nb gene sets
## 1                           50
## 2         CP:KEGG          186
## 3          CP:PID          196
## 4     CP:REACTOME         1615
## 5 CP:WIKIPATHWAYS          664
## 6           GO:BP         7658
## 7           GO:CC         1006
## 8           GO:MF         1738

We get gene name and gene ID correspondence :

gene_corresp = sobj@assays[["RNA"]]@meta.features[, c("gene_name", "Ensembl_ID")] %>%
  `colnames<-`(c("NAME", "ID")) %>%
  dplyr::mutate(ID = as.character(ID))
rownames(gene_corresp) = gene_corresp$ID

head(gene_corresp)
##                       NAME              ID
## ENSG00000238009 AL627309.1 ENSG00000238009
## ENSG00000237491 AL669831.5 ENSG00000237491
## ENSG00000225880  LINC00115 ENSG00000225880
## ENSG00000230368     FAM41C ENSG00000230368
## ENSG00000230699 AL645608.3 ENSG00000230699
## ENSG00000187634     SAMD11 ENSG00000187634

ORS vs IFE basal

group_name = "ORS_vs_IFE_basal"

We change cell identities to cluster type :

Seurat::Idents(sobj) = sobj$cluster_type

table(Seurat::Idents(sobj), sobj$sample_type)
##            
##               HS   HD
##   IFE basal 1480  434
##   ORS       1273  229

DE

We identify specific markers for each population :

mark = Seurat::FindMarkers(sobj, ident.1 = "ORS", ident.2 = "IFE basal")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[[group_name]]$mark = mark

dim(mark)
## [1] 1020    5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2     p_val_adj
## KRT16     0.000000e+00  4.309131 0.921 0.118  0.000000e+00
## KRT6B     0.000000e+00  3.656597 0.923 0.293  0.000000e+00
## FABP5     0.000000e+00  3.565441 0.993 0.498  0.000000e+00
## KRT6A    3.240362e-159  3.061127 0.774 0.485 5.405896e-155
## KRT17     0.000000e+00  3.035906 0.989 0.750  0.000000e+00
## CST6     5.331855e-159  2.836067 0.398 0.037 8.895133e-155
## KRT6C    1.618794e-265  2.621388 0.571 0.044 2.700635e-261
## TMSB4X    0.000000e+00  2.393617 0.992 0.878  0.000000e+00
## LGALS1    0.000000e+00  2.355597 0.923 0.486  0.000000e+00
## S100A2    0.000000e+00  2.104855 0.995 0.991  0.000000e+00
## CALML3    0.000000e+00  2.010035 0.972 0.704  0.000000e+00
## GJA1      0.000000e+00  1.956189 0.872 0.607  0.000000e+00
## GJB6      0.000000e+00  1.835009 0.913 0.690  0.000000e+00
## SBSN     7.327194e-244  1.831239 0.543 0.043 1.222396e-239
## APOE      0.000000e+00  1.745727 0.983 0.790  0.000000e+00
## NDUFA4L2 9.107385e-221  1.700887 0.693 0.231 1.519385e-216
## LYPD3     0.000000e+00  1.672332 0.742 0.143  0.000000e+00
## TUBB2A    0.000000e+00  1.661309 0.760 0.191  0.000000e+00
## SDC1     8.945403e-297  1.650168 0.838 0.542 1.492362e-292
## HSPB1     0.000000e+00  1.614432 0.990 0.967  0.000000e+00

There are 1020 genes differentially expressed. How many for each population ?

# ORS
mark_ibl = mark %>%
  dplyr::filter(avg_logFC > 0)
nrow(mark_ibl)
## [1] 415
# IFE basal
mark_ors = mark %>%
  dplyr::filter(avg_logFC < 0)
nrow(mark_ors)
## [1] 605

We represent the information on a figure :

mark$gene_name = rownames(mark)
mark_to_label = rbind(
  # up-regulated in ORS
  mark %>% dplyr::top_n(., n = 20, wt = avg_logFC),
  # up-regulated in IFE basal
  mark %>% dplyr::top_n(., n = 20, wt = -avg_logFC),
  # representative and selective for ORS
  mark %>% dplyr::top_n(., n = 20, wt = (pct.1 - pct.2)),
  # representative and selective for IFE basal
  mark %>% dplyr::top_n(., n = 20, wt = -(pct.1 - pct.2))) %>%
  dplyr::distinct()

ggplot2::ggplot(mark, aes(x = pct.1, y = pct.2, col = avg_logFC)) +
  ggplot2::geom_abline(slope = 1, intercept = 0, lty = 2) +
  ggplot2::geom_point() +
  ggrepel::geom_label_repel(data = mark_to_label, max.overlaps = Inf,
                            aes(x = pct.1, y = pct.2, label = gene_name),
                            col = "black", fill = NA, size = 3, label.size = NA) +
  ggplot2::labs(title = "Differentially expressed genes",
                subtitle = "between ORS and IFE basal") +
  ggplot2::scale_color_gradient2(low = aquarius::color_cnv[1],
                                 mid = aquarius::color_cnv[2],
                                 high = aquarius::color_cnv[3],
                                 midpoint = 0) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5))

We represent the best genes on a violin plot, group by cluster type and split by sample type :

mark_to_label = mark_to_label %>%
  dplyr::arrange(pct.1 - pct.2)

plot_list = lapply(mark_to_label$gene_name, FUN = function(gene) {
  p = Seurat::VlnPlot(sobj, features = gene, pt.size = 0.001,
                      group.by = "cluster_type",
                      split.by = "sample_type") +
    ggplot2::scale_fill_manual(breaks = c("HS", "HD"),
                               values = c("#C55F40", "#2C78E6")) +
    ggplot2::theme(axis.title.x = element_blank(),
                   axis.title.y = element_blank(),
                   legend.position = "none")
  return(p)
})

patchwork::wrap_plots(plot_list, ncol = 5)

On the figure, we can see that some specific markers for a population are indeed specific to a sample type rather than the whole population.

ORS

We explore enrichment in gene sets for ORS population

genes_of_interest = rownames(mark_ibl)

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Up-regulated in ORS compared to IFE basal")

list_results[[group_name]]$enrichr_ibl = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

IFE basal

We explore enrichment in gene sets for IFE basal population.

genes_of_interest = rownames(mark_ors)

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Up-regulated in IFE basal compared to ORS")

list_results[[group_name]]$enrichr_ors = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

GSEA

We run a GSEA for all gene sets, from the full count matrix :

ranked_gene_list = aquarius::run_foldchange(Seurat::GetAssayData(sobj, assay = "RNA", slot = "counts"),
                                            group1 = colnames(sobj)[sobj@active.ident %in% "ORS"],
                                            group2 = colnames(sobj)[sobj@active.ident %in% "IFE basal"])
names(ranked_gene_list) = gene_corresp$ID

gsea_results = aquarius::gsea_run(ranked_gene_list = ranked_gene_list,
                                  gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                  GSEA_p_val_thresh = 1)

list_results[[group_name]]$gsea = gsea_results

gsea_results@result %>%
  dplyr::filter(pvalue < 0.05) %>%
  dplyr::top_n(., n = 200, wt = abs(NES)) %>%
  dplyr::mutate(too_long = ifelse(nchar(ID) > 60, yes = TRUE, no = FALSE)) %>%
  dplyr::mutate(ID = stringr::str_sub(ID, end = 60)) %>%
  dplyr::mutate(ID = ifelse(too_long, yes = paste0(ID, "..."), no = ID)) %>%
  aquarius::gsea_plot(show_legend = TRUE) +
  ggplot2::labs(title = "GSEA using all genes (count matrix)") +
  ggplot2::theme(plot.title = element_text(size = 20))

We make the gsea plot for two gene sets :

p1 = enrichplot::gseaplot2(x = gsea_results, geneSetID = "REACTOME_KERATINIZATION") +
  ggplot2::labs(title = "REACTOME_KERATINIZATION") +
  ggplot2::theme(plot.title = element_text(hjust = 0.5, face = "bold",
                                           margin = ggplot2::margin(3, 3, 5, 3)))

p2 = enrichplot::gseaplot2(x = gsea_results, geneSetID = "HALLMARK_INTERFERON_GAMMA_RESPONSE") +
  ggplot2::labs(title = "HALLMARK_INTERFERON_GAMMA_RESPONSE") +
  ggplot2::theme(plot.title = element_text(hjust = 0.5, face = "bold",
                                           margin = ggplot2::margin(3, 3, 5, 3)))

p1 | p2

Maybe those gene sets are specific to sample type rather than the whole cell population.

Cluster 4 within IFE basal

group_name = "cluster4_vs_IFE basal"

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "IFE basal")
subsobj$seurat_clusters = base::droplevels(subsobj$seurat_clusters)
Seurat::Idents(subsobj) = subsobj$seurat_clusters

table(subsobj$seurat_clusters)
## 
##   0   2   3   4  10 
## 667 430 363 346 108

DE

We identify specific markers for each population :

mark = Seurat::FindMarkers(subsobj, ident.1 = 4)

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[[group_name]]$mark = mark

dim(mark)
## [1] 380   5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2     p_val_adj
## SLPI     3.255137e-130 1.5256517 0.743 0.160 5.430545e-126
## MT1X      3.820149e-66 1.4018462 0.922 0.700  6.373155e-62
## KRT15    3.317107e-130 1.3213954 1.000 0.902 5.533930e-126
## LY6D      2.225488e-78 1.2305062 0.460 0.078  3.712781e-74
## IFI27     2.254730e-96 1.1549038 0.526 0.081  3.761567e-92
## MT1E      3.852025e-76 1.1401782 0.948 0.672  6.426333e-72
## IGFBP3   1.683129e-119 1.1290859 0.393 0.015 2.807964e-115
## CXCL14    1.260295e-81 1.0964601 0.994 0.846  2.102550e-77
## WNT3     1.472048e-209 1.0518440 0.890 0.128 2.455818e-205
## AQP3      1.223832e-78 0.9765986 0.957 0.763  2.041720e-74
## IL1R2    9.284308e-129 0.9476709 0.659 0.103 1.548901e-124
## TSC22D3   2.182105e-70 0.9196802 0.708 0.263  3.640406e-66
## NEAT1     1.132711e-43 0.8746776 0.980 0.944  1.889702e-39
## MT2A      8.775261e-28 0.8724487 0.983 0.876  1.463977e-23
## MTRNR2L1  2.478891e-24 0.8503250 0.783 0.621  4.135533e-20
## ZFP36     3.636528e-34 0.8387802 0.702 0.429  6.066820e-30
## GLUL      7.288508e-51 0.8327581 0.908 0.736  1.215942e-46
## ID1       1.124799e-12 0.8121904 0.624 0.466  1.876502e-08
## IL18      7.785633e-97 0.8100874 0.942 0.476  1.298877e-92
## HOPX      1.782476e-68 0.7958538 0.832 0.434  2.973705e-64

There are 380 genes differentially expressed. How many for each population ?

# cluster 4
mark_cluster4 = mark %>%
  dplyr::filter(avg_logFC > 0)
nrow(mark_cluster4)
## [1] 209
# Other IFE basal
mark_not5 = mark %>%
  dplyr::filter(avg_logFC < 0)
nrow(mark_not5)
## [1] 171

We represent the information on a figure :

mark$gene_name = rownames(mark)
mark_signif = rbind(
  # up-regulated in cluster 4
  mark %>% dplyr::top_n(., n = 20, wt = avg_logFC),
  # up-regulated in other IFE basal
  mark %>% dplyr::top_n(., n = 20, wt = -avg_logFC),
  # representative and selective for cluster 4
  mark %>% dplyr::top_n(., n = 20, wt = (pct.1 - pct.2)),
  # representative and selective for other IFE basal
  mark %>% dplyr::top_n(., n = 20, wt = -(pct.1 - pct.2))) %>%
  dplyr::distinct()

ggplot2::ggplot(mark, aes(x = pct.1, y = pct.2, col = avg_logFC)) +
  ggplot2::geom_abline(slope = 1, intercept = 0, lty = 2) +
  ggplot2::geom_point() +
  ggrepel::geom_label_repel(data = mark_signif, max.overlaps = Inf,
                            aes(x = pct.1, y = pct.2, label = gene_name),
                            col = "black", fill = NA, size = 3, label.size = NA) +
  ggplot2::labs(title = "Differentially expressed genes",
                subtitle = "between cluster 4 and other IFE basal") +
  ggplot2::scale_color_gradient2(low = aquarius::color_cnv[1],
                                 mid = aquarius::color_cnv[2],
                                 high = aquarius::color_cnv[3],
                                 midpoint = 0) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5))

Heatmap

We represent a subset of genes on a heatmap :

features_oi = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::filter(abs(avg_logFC) > 0.5) %>%
  rownames()

length(features_oi)
## [1] 103

We prepare the scaled expression matrix :

mat_expression = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "data")[features_oi, ]
mat_expression = Matrix::t(mat_expression)
mat_expression = dynutils::scale_quantile(mat_expression) # between 0 and 1
mat_expression = Matrix::t(mat_expression)
mat_expression = as.matrix(mat_expression) # not sparse

dim(mat_expression)
## [1]  103 1914

We prepare the heatmap annotation :

ha_top = ComplexHeatmap::HeatmapAnnotation(
  cluster_type = subsobj$cluster_type,
  sample_type = subsobj$sample_type,
  cluster = subsobj$seurat_clusters,
  col = list(cluster_type = color_markers,
             sample_type = setNames(nm = c("HS", "HD"),
                                    c("#C55F40", "#2C78E6")),
             cluster = setNames(nm = levels(subsobj$seurat_clusters),
                                aquarius::gg_color_hue(length(levels(subsobj$seurat_clusters))))))

And the heatmap :

ht = ComplexHeatmap::Heatmap(mat_expression,
                             col = aquarius::color_cnv,
                             # Annotation
                             top_annotation = ha_top,
                             # Grouping
                             column_title = NULL,
                             cluster_rows = TRUE,
                             cluster_columns = TRUE,
                             show_column_names = FALSE,
                             # Visual aspect
                             show_heatmap_legend = TRUE,
                             border = TRUE)

ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "bottom",
                     annotation_legend_side = "bottom")

Up-regulated in cluster 4

We explore enrichment in gene sets for cluster 4.

genes_of_interest = rownames(mark_cluster4)

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Up-regulated in cluster 4 compared to other IFE basal")

list_results[[group_name]]$enrichr_up = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

Down-regulated in cluster 4

We explore enrichment in gene sets for genes downregulated in cluster 4.

genes_of_interest = rownames(mark_not5)

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Down-regulated in cluster 4 compared to other IFE basal")

list_results[[group_name]]$enrichr_dn = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

GSEA

We run a GSEA for all gene sets, from the full count matrix :

ranked_gene_list = aquarius::run_foldchange(Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts"),
                                            group1 = colnames(subsobj)[subsobj@active.ident == 5],
                                            group2 = colnames(subsobj)[subsobj@active.ident != 5])
names(ranked_gene_list) = gene_corresp$ID

gsea_results = aquarius::gsea_run(ranked_gene_list = ranked_gene_list,
                                  gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                  GSEA_p_val_thresh = 1)

list_results[[group_name]]$gsea = gsea_results

gsea_results@result %>%
  dplyr::filter(pvalue < 0.05) %>%
  dplyr::top_n(., n = 200, wt = abs(NES)) %>%
  dplyr::mutate(too_long = ifelse(nchar(ID) > 60, yes = TRUE, no = FALSE)) %>%
  dplyr::mutate(ID = stringr::str_sub(ID, end = 60)) %>%
  dplyr::mutate(ID = ifelse(too_long, yes = paste0(ID, "..."), no = ID)) %>%
  aquarius::gsea_plot(show_legend = TRUE) +
  ggplot2::labs(title = "GSEA using all genes (count matrix)") +
  ggplot2::theme(plot.title = element_text(size = 20))

We compute a fold change table to make a wordcloud :

fold_change = gene_corresp
colnames(fold_change) = c("gene_name", "ID")
rownames(fold_change) = fold_change$ID
fold_change$FC = ranked_gene_list[rownames(fold_change)]
fold_change$pct.1 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident == "5"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$pct.2 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident != "5"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$FC_x_pct = ifelse(fold_change$FC > 0,
                              yes = fold_change$FC * fold_change$pct.1,
                              no = fold_change$FC * fold_change$pct.2)

dim(fold_change) ; head(fold_change)
## [1] 16683     6
##                  gene_name              ID       FC pct.1       pct.2 FC_x_pct
## ENSG00000238009 AL627309.1 ENSG00000238009 7.616614   NaN 0.005224660      NaN
## ENSG00000237491 AL669831.5 ENSG00000237491 3.675166   NaN 0.075757576      NaN
## ENSG00000225880  LINC00115 ENSG00000225880 4.847226   NaN 0.037617555      NaN
## ENSG00000230368     FAM41C ENSG00000230368 4.053677   NaN 0.063218391      NaN
## ENSG00000230699 AL645608.3 ENSG00000230699 7.268690   NaN 0.006792059      NaN
## ENSG00000187634     SAMD11 ENSG00000187634 8.268690   NaN 0.003134796      NaN

We make the gsea plot for some gene sets :

gs_oi = c("GOBP_CD4_POSITIVE_ALPHA_BETA_T_CELL_DIFFERENTIATION",
          "GOBP_REGULATION_OF_ALPHA_BETA_T_CELL_DIFFERENTIATION",
          "GOBP_ALPHA_BETA_T_CELL_DIFFERENTIATION",
          "GOBP_POSITIVE_REGULATION_OF_ALPHA_BETA_T_CELL_ACTIVATION",
          "GOBP_ALPHA_BETA_T_CELL_ACTIVATION")

plot_list = make_gsea_plot(gsea_results, gs_oi, fold_change, "FC_x_pct")

patchwork::wrap_plots(plot_list, ncol = 2, widths = c(2,1.5))

ORS : HS vs HD

group_name = "ORS_HS_vs_HD"

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "ORS")
Seurat::Idents(subsobj) = subsobj$sample_type

table(subsobj$sample_type)
## 
##   HS   HD 
## 1273  229

DE

We identify DE genes between HS and HD :

mark = Seurat::FindMarkers(subsobj, ident.1 = "HS", ident.2 = "HD")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[[group_name]]$mark = mark

dim(mark)
## [1] 113   5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2    p_val_adj
## LGALS7    1.233391e-50 1.6382773 0.687 0.188 2.057666e-46
## S100A7    2.238581e-12 1.6315223 0.301 0.079 3.734624e-08
## MIF       2.628565e-45 1.2219076 0.706 0.258 4.385235e-41
## LGALS7B   1.047084e-10 1.1858854 0.776 0.712 1.746850e-06
## MTRNR2L12 9.689280e-12 1.0610589 0.383 0.175 1.616463e-07
## MT-CO1    3.687275e-12 1.0034803 0.440 0.218 6.151481e-08
## MTRNR2L8  1.016248e-11 0.9191119 0.347 0.148 1.695406e-07
## FOSB      2.189561e-09 0.8917793 0.299 0.127 3.652844e-05
## MT-CO2    2.150331e-08 0.8575475 0.423 0.262 3.587398e-04
## S100A9    3.885050e-14 0.8421403 0.321 0.074 6.481429e-10
## NDUFA4L2  2.189998e-21 0.8332238 0.742 0.424 3.653574e-17
## CA2       1.798407e-17 0.6754404 0.464 0.170 3.000282e-13
## ARF5      9.132684e-28 0.6267638 0.435 0.044 1.523606e-23
## MT-ATP6   1.503327e-06 0.6204255 0.487 0.349 2.508001e-02
## MT-ND4    2.221289e-07 0.6035045 0.402 0.240 3.705776e-03
## CKB       3.042386e-08 0.5733551 0.288 0.118 5.075613e-04
## MALAT1    1.510247e-06 0.5431476 0.696 0.572 2.519545e-02
## S100A8    1.745757e-08 0.5268172 0.194 0.039 2.912446e-04
## MCL1      6.046367e-08 0.5068625 0.380 0.205 1.008715e-03
## KRT15     1.756490e-07 0.5054360 0.282 0.118 2.930352e-03

Up in HS

We explore enrichment in gene sets for HS population.

genes_of_interest = mark %>%
  dplyr::filter(avg_logFC > 0) %>%
  rownames()

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Up-regulated in HS compared to HD")

list_results[[group_name]]$enrichr_hs = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

Up in HD

We explore enrichment in gene sets for HD population.

genes_of_interest = mark %>%
  dplyr::filter(avg_logFC < 0) %>%
  rownames()

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Down-regulated in HS compared to HD")

list_results[[group_name]]$enrichr_hd = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

GSEA

We run a GSEA for all gene sets, from the full count matrix :

ranked_gene_list = aquarius::run_foldchange(Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts"),
                                            group1 = colnames(subsobj)[subsobj@active.ident %in% "HS"],
                                            group2 = colnames(subsobj)[subsobj@active.ident %in% "HD"])
names(ranked_gene_list) = gene_corresp$ID

gsea_results = aquarius::gsea_run(ranked_gene_list = ranked_gene_list,
                                  gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                  GSEA_p_val_thresh = 1)

list_results[[group_name]]$gsea = gsea_results

gsea_results@result %>%
  dplyr::filter(pvalue < 0.05) %>%
  dplyr::top_n(., n = 200, wt = abs(NES)) %>%
  dplyr::mutate(too_long = ifelse(nchar(ID) > 60, yes = TRUE, no = FALSE)) %>%
  dplyr::mutate(ID = stringr::str_sub(ID, end = 60)) %>%
  dplyr::mutate(ID = ifelse(too_long, yes = paste0(ID, "..."), no = ID)) %>%
  aquarius::gsea_plot(show_legend = TRUE) +
  ggplot2::labs(title = "GSEA using all genes (count matrix)") +
  ggplot2::theme(plot.title = element_text(size = 20))

We compute a fold change table to make a wordcloud :

fold_change = gene_corresp
colnames(fold_change) = c("gene_name", "ID")
rownames(fold_change) = fold_change$ID
fold_change$FC = ranked_gene_list[rownames(fold_change)]
fold_change$pct.1 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident == "HS"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$pct.2 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident == "HD"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$FC_x_pct = ifelse(fold_change$FC > 0,
                              yes = fold_change$FC * fold_change$pct.1,
                              no = fold_change$FC * fold_change$pct.2)

dim(fold_change) ; head(fold_change)
## [1] 16683     6
##                  gene_name              ID         FC       pct.1       pct.2
## ENSG00000238009 AL627309.1 ENSG00000238009 -0.7927899 0.004713276 0.000000000
## ENSG00000237491 AL669831.5 ENSG00000237491  0.1788283 0.092694423 0.039301310
## ENSG00000225880  LINC00115 ENSG00000225880 -1.0151823 0.021209741 0.017467249
## ENSG00000230368     FAM41C ENSG00000230368  1.1002949 0.038491752 0.004366812
## ENSG00000230699 AL645608.3 ENSG00000230699 -0.6001448 0.005498822 0.000000000
## ENSG00000187634     SAMD11 ENSG00000187634 -3.0151823 0.001571092 0.004366812
##                    FC_x_pct
## ENSG00000238009  0.00000000
## ENSG00000237491  0.01657639
## ENSG00000225880 -0.01773244
## ENSG00000230368  0.04235228
## ENSG00000230699  0.00000000
## ENSG00000187634 -0.01316673

We make the gsea plot for some gene sets :

gs_oi = c("REACTOME_EXPORT_OF_VIRAL_RIBONUCLEOPROTEINS_FROM_NUCLEUS",
          "GOBP_MICROTUBULE_ANCHORING",
          "GOBP_HISTONE_H3_K4_TRIMETHYLATION",
          "GOBP_HISTONE_H3_K4_MONOMETHYLATION",
          "GOBP_MITOPHAGY",
          "REACTOME_SEROTONIN_NEUROTRANSMITTER_RELEASE_CYCLE",
          "REACTOME_DOPAMINE_NEUROTRANSMITTER_RELEASE_CYCLE")

plot_list = make_gsea_plot(gsea_results, gs_oi, fold_change, "FC_x_pct")

patchwork::wrap_plots(plot_list, ncol = 2, widths = c(2,1.5))

IFE basal : HS vs HD

group_name = "IFE basal_HS_vs_HD"

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "IFE basal")
Seurat::Idents(subsobj) = subsobj$sample_type

table(subsobj$sample_type)
## 
##   HS   HD 
## 1480  434

DE

We identify DE genes between HS and HD :

mark = Seurat::FindMarkers(subsobj, ident.1 = "HS", ident.2 = "HD")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[[group_name]]$mark = mark

dim(mark)
## [1] 156   5
head(mark, n = 20)
##                   p_val avg_logFC pct.1 pct.2     p_val_adj
## S100A7     2.410179e-31 1.3566760 0.388 0.088  4.020902e-27
## S100A9     7.080629e-73 1.3078406 0.841 0.366  1.181261e-68
## S100A8     5.506555e-56 1.1196442 0.788 0.371  9.186586e-52
## RPS26     1.089423e-158 1.0419864 0.980 0.963 1.817485e-154
## LGALS7B    4.507541e-27 0.9101773 0.656 0.525  7.519930e-23
## CCL2       1.474907e-35 0.8509353 0.620 0.295  2.460588e-31
## FABP5      2.291460e-12 0.8090332 0.533 0.380  3.822842e-08
## LTF        6.641929e-17 0.7315313 0.186 0.023  1.108073e-12
## MTRNR2L8   4.615073e-43 0.7159748 0.939 0.947  7.699326e-39
## IFI27      2.285749e-19 0.7070176 0.202 0.023  3.813316e-15
## TIMP1      7.070729e-25 0.6009821 0.589 0.364  1.179610e-20
## IFITM3     4.146261e-52 0.5555848 0.922 0.857  6.917207e-48
## CD74       1.999758e-26 0.5376344 0.361 0.101  3.336197e-22
## AKR1B10    8.228382e-44 0.5193093 0.389 0.035  1.372741e-39
## HLA-C      1.422701e-23 0.5147398 0.758 0.578  2.373492e-19
## CXCL14     6.606512e-15 0.4968242 0.889 0.816  1.102164e-10
## MIF        7.372684e-27 0.4964571 0.409 0.168  1.229985e-22
## MTRNR2L10  1.455945e-12 0.4914428 0.619 0.569  2.428953e-08
## MTRNR2L12  6.611841e-25 0.4469115 0.968 0.982  1.103053e-20
## CLCA2      1.199172e-24 0.4257155 0.686 0.523  2.000578e-20

Up in HS

We explore enrichment in gene sets for HS population.

genes_of_interest = mark %>%
  dplyr::filter(avg_logFC > 0) %>%
  rownames()

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Up-regulated in HS compared to HD")

list_results[[group_name]]$enrichr_hs = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

Up in HD

We explore enrichment in gene sets for HD population.

genes_of_interest = mark %>%
  dplyr::filter(avg_logFC < 0) %>%
  rownames()

enrichr_results = aquarius::run_enrichr(gene_names = genes_of_interest,
                                        gene_corresp = gene_corresp,
                                        gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                        make_plot = TRUE,
                                        plot_title = "Down-regulated in HS compared to HD")

list_results[[group_name]]$enrichr_hd = enrichr_results$ego

enrichr_results$plot +
  ggplot2::theme(axis.text.y = element_text(size = 8))

GSEA

We run a GSEA for all gene sets, from the full count matrix :

ranked_gene_list = aquarius::run_foldchange(Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts"),
                                            group1 = colnames(subsobj)[subsobj@active.ident %in% "HS"],
                                            group2 = colnames(subsobj)[subsobj@active.ident %in% "HD"])
names(ranked_gene_list) = gene_corresp$ID

gsea_results = aquarius::gsea_run(ranked_gene_list = ranked_gene_list,
                                  gene_sets = gene_sets[, c("gs_name", "ensembl_gene")],
                                  GSEA_p_val_thresh = 1)

list_results[[group_name]]$gsea = gsea_results

gsea_results@result %>%
  dplyr::filter(pvalue < 0.05) %>%
  dplyr::top_n(., n = 200, wt = abs(NES)) %>%
  dplyr::mutate(too_long = ifelse(nchar(ID) > 60, yes = TRUE, no = FALSE)) %>%
  dplyr::mutate(ID = stringr::str_sub(ID, end = 60)) %>%
  dplyr::mutate(ID = ifelse(too_long, yes = paste0(ID, "..."), no = ID)) %>%
  aquarius::gsea_plot(show_legend = TRUE) +
  ggplot2::labs(title = "GSEA using all genes (count matrix)") +
  ggplot2::theme(plot.title = element_text(size = 20))

We compute a fold change table to make a wordcloud :

fold_change = gene_corresp
colnames(fold_change) = c("gene_name", "ID")
rownames(fold_change) = fold_change$ID
fold_change$FC = ranked_gene_list[rownames(fold_change)]
fold_change$pct.1 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident == "HS"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$pct.2 = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "counts")[, subsobj@active.ident == "HD"] %>%
  apply(., MARGIN = 1, FUN = function(one_row) {
    return( mean(one_row != 0) )
  })
fold_change$FC_x_pct = ifelse(fold_change$FC > 0,
                              yes = fold_change$FC * fold_change$pct.1,
                              no = fold_change$FC * fold_change$pct.2)

dim(fold_change) ; head(fold_change)
## [1] 16683     6
##                  gene_name              ID         FC       pct.1       pct.2
## ENSG00000238009 AL627309.1 ENSG00000238009 -0.2321825 0.005405405 0.004608295
## ENSG00000237491 AL669831.5 ENSG00000237491  0.7189079 0.083783784 0.048387097
## ENSG00000225880  LINC00115 ENSG00000225880  0.4596952 0.040540541 0.027649770
## ENSG00000230368     FAM41C ENSG00000230368  0.1284072 0.064864865 0.057603687
## ENSG00000230699 AL645608.3 ENSG00000230699 -0.8171450 0.006081081 0.009216590
## ENSG00000187634     SAMD11 ENSG00000187634 -1.8171450 0.002027027 0.006912442
##                     FC_x_pct
## ENSG00000238009 -0.001069965
## ENSG00000237491  0.060232826
## ENSG00000225880  0.018636293
## ENSG00000230368  0.008329118
## ENSG00000230699 -0.007531290
## ENSG00000187634 -0.012560910

We make the gsea plot for some gene sets :

gs_oi = c("GOBP_RESPONSE_TO_MOLECULE_OF_BACTERIAL_ORIGIN",
          "GOBP_HUMORAL_IMMUNE_RESPONSE",
          "HALLMARK_APOPTOSIS",
          "GOBP_GRANULOCYTE_CHEMOTAXIS",
          "GOBP_GRANULOCYTE_MIGRATION",
          "HALLMARK_INTERFERON_ALPHA_RESPONSE",
          "HALLMARK_INTERFERON_GAMMA_RESPONSE",
          "HALLMARK_TNFA_SIGNALING_VIA_NFKB",
          "HALLMARK_HYPOXIA",
          "GOBP_RESPONSE_TO_TRANSFORMING_GROWTH_FACTOR_BETA")

plot_list = make_gsea_plot(gsea_results, gs_oi, fold_change, "FC_x_pct")

patchwork::wrap_plots(plot_list, ncol = 2, widths = c(2,1.5))

Global heatmap

We represent all differentially expressed genes on a heatmap. First, we extract all DE genes :

features_oi = c(mark_to_label$gene_name,
                rownames(list_results$ORS_HS_vs_HD$mark),
                rownames(list_results$IFE_basal_HS_vs_HD$mark)) %>%
  unique()

length(features_oi)
## [1] 155

We prepare the scaled expression matrix :

mat_expression = Seurat::GetAssayData(sobj, assay = "RNA", slot = "data")[features_oi, ]
mat_expression = Matrix::t(mat_expression)
mat_expression = dynutils::scale_quantile(mat_expression) # between 0 and 1
mat_expression = Matrix::t(mat_expression)
mat_expression = as.matrix(mat_expression) # not sparse

dim(mat_expression)
## [1]  155 3416

We prepare the heatmap annotation :

ha_top = ComplexHeatmap::HeatmapAnnotation(
  cluster_type = sobj$cluster_type,
  sample_type = sobj$sample_type,
  cluster = sobj$seurat_clusters,
  col = list(cluster_type = color_markers,
             sample_type = setNames(nm = c("HS", "HD"),
                                    c("#C55F40", "#2C78E6")),
             cluster = setNames(nm = levels(sobj$seurat_clusters),
                                aquarius::gg_color_hue(length(levels(sobj$seurat_clusters))))))

And the heatmap :

sobj$cell_group = paste0(sobj$cluster_type, sobj$sample_type) %>%
  as.factor()

ht = ComplexHeatmap::Heatmap(mat_expression,
                             col = aquarius::color_cnv,
                             # Annotation
                             top_annotation = ha_top,
                             # Grouping
                             column_order = sobj@meta.data %>%
                               dplyr::arrange(cluster_type, sample_type, seurat_clusters) %>%
                               rownames(),
                             column_split = sobj$cell_group,
                             column_gap = unit(c(0.01, 2, 0.01), "mm"),
                             column_title = NULL,
                             cluster_rows = TRUE,
                             cluster_columns = FALSE,
                             show_column_names = FALSE,
                             # Visual aspect
                             show_heatmap_legend = TRUE,
                             border = TRUE)

ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "bottom",
                     annotation_legend_side = "bottom")

Save

We save the list of results :

saveRDS(list_results, file = paste0(out_dir, "/", save_name, "_list_results.rds"))

R Session

show
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.6 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so
## LAPACK: /usr/local/lib/R/lib/libRlapack.so
## 
## locale:
## [1] C
## 
## attached base packages:
## [1] grid      stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
## [1] ComplexHeatmap_2.14.0 ggplot2_3.3.5         patchwork_1.1.2      
## [4] dplyr_1.0.7          
## 
## loaded via a namespace (and not attached):
##   [1] softImpute_1.4              graphlayouts_0.7.0         
##   [3] pbapply_1.4-2               lattice_0.20-41            
##   [5] haven_2.3.1                 vctrs_0.3.8                
##   [7] usethis_2.0.1               dynwrap_1.2.1              
##   [9] blob_1.2.1                  survival_3.2-13            
##  [11] prodlim_2019.11.13          dynutils_1.0.5             
##  [13] later_1.3.0                 DBI_1.1.1                  
##  [15] R.utils_2.11.0              SingleCellExperiment_1.8.0 
##  [17] rappdirs_0.3.3              uwot_0.1.8                 
##  [19] dqrng_0.2.1                 jpeg_0.1-8.1               
##  [21] zlibbioc_1.32.0             pspline_1.0-18             
##  [23] pcaMethods_1.78.0           mvtnorm_1.1-1              
##  [25] htmlwidgets_1.5.4           GlobalOptions_0.1.2        
##  [27] future_1.22.1               UpSetR_1.4.0               
##  [29] laeken_0.5.2                leiden_0.3.3               
##  [31] clustree_0.4.3              parallel_3.6.3             
##  [33] scater_1.14.6               irlba_2.3.3                
##  [35] markdown_1.1                DEoptimR_1.0-9             
##  [37] tidygraph_1.1.2             Rcpp_1.0.9                 
##  [39] readr_2.0.2                 KernSmooth_2.23-17         
##  [41] carrier_0.1.0               promises_1.1.0             
##  [43] gdata_2.18.0                DelayedArray_0.12.3        
##  [45] limma_3.42.2                graph_1.64.0               
##  [47] RcppParallel_5.1.4          Hmisc_4.4-0                
##  [49] fs_1.5.2                    RSpectra_0.16-0            
##  [51] fastmatch_1.1-0             ranger_0.12.1              
##  [53] digest_0.6.25               png_0.1-7                  
##  [55] sctransform_0.2.1           cowplot_1.0.0              
##  [57] DOSE_3.12.0                 here_1.0.1                 
##  [59] TInGa_0.0.0.9000            ggraph_2.0.3               
##  [61] pkgconfig_2.0.3             GO.db_3.10.0               
##  [63] DelayedMatrixStats_1.8.0    gower_0.2.1                
##  [65] ggbeeswarm_0.6.0            iterators_1.0.12           
##  [67] DropletUtils_1.6.1          reticulate_1.26            
##  [69] clusterProfiler_3.14.3      SummarizedExperiment_1.16.1
##  [71] circlize_0.4.15             beeswarm_0.4.0             
##  [73] GetoptLong_1.0.5            xfun_0.35                  
##  [75] bslib_0.3.1                 zoo_1.8-10                 
##  [77] tidyselect_1.1.0            reshape2_1.4.4             
##  [79] purrr_0.3.4                 ica_1.0-2                  
##  [81] pcaPP_1.9-73                viridisLite_0.3.0          
##  [83] rtracklayer_1.46.0          rlang_1.0.2                
##  [85] hexbin_1.28.1               jquerylib_0.1.4            
##  [87] dyneval_0.9.9               glue_1.4.2                 
##  [89] RColorBrewer_1.1-2          matrixStats_0.56.0         
##  [91] stringr_1.4.0               lava_1.6.7                 
##  [93] europepmc_0.3               DESeq2_1.26.0              
##  [95] recipes_0.1.17              labeling_0.3               
##  [97] httpuv_1.5.2                class_7.3-17               
##  [99] BiocNeighbors_1.4.2         DO.db_2.9                  
## [101] annotate_1.64.0             jsonlite_1.7.2             
## [103] XVector_0.26.0              bit_4.0.4                  
## [105] mime_0.9                    aquarius_0.1.5             
## [107] Rsamtools_2.2.3             gridExtra_2.3              
## [109] gplots_3.0.3                stringi_1.4.6              
## [111] processx_3.5.2              gsl_2.1-6                  
## [113] bitops_1.0-6                cli_3.0.1                  
## [115] batchelor_1.2.4             RSQLite_2.2.0              
## [117] randomForest_4.6-14         tidyr_1.1.4                
## [119] data.table_1.14.2           rstudioapi_0.13            
## [121] org.Mm.eg.db_3.10.0         GenomicAlignments_1.22.1   
## [123] nlme_3.1-147                qvalue_2.18.0              
## [125] scran_1.14.6                locfit_1.5-9.4             
## [127] scDblFinder_1.1.8           listenv_0.8.0              
## [129] ggthemes_4.2.4              gridGraphics_0.5-0         
## [131] R.oo_1.24.0                 dbplyr_1.4.4               
## [133] BiocGenerics_0.32.0         TTR_0.24.2                 
## [135] readxl_1.3.1                lifecycle_1.0.1            
## [137] timeDate_3043.102           ggpattern_0.3.1            
## [139] munsell_0.5.0               cellranger_1.1.0           
## [141] R.methodsS3_1.8.1           proxyC_0.1.5               
## [143] visNetwork_2.0.9            caTools_1.18.0             
## [145] codetools_0.2-16            ggwordcloud_0.5.0          
## [147] Biobase_2.46.0              GenomeInfoDb_1.22.1        
## [149] vipor_0.4.5                 lmtest_0.9-38              
## [151] msigdbr_7.5.1               htmlTable_1.13.3           
## [153] triebeard_0.3.0             lsei_1.2-0                 
## [155] xtable_1.8-4                ROCR_1.0-7                 
## [157] BiocManager_1.30.10         scatterplot3d_0.3-41       
## [159] abind_1.4-5                 farver_2.0.3               
## [161] parallelly_1.28.1           RANN_2.6.1                 
## [163] askpass_1.1                 GenomicRanges_1.38.0       
## [165] RcppAnnoy_0.0.16            tibble_3.1.5               
## [167] ggdendro_0.1-20             cluster_2.1.0              
## [169] future.apply_1.5.0          Seurat_3.1.5               
## [171] dendextend_1.15.1           Matrix_1.3-2               
## [173] ellipsis_0.3.2              prettyunits_1.1.1          
## [175] lubridate_1.7.9             ggridges_0.5.2             
## [177] igraph_1.2.5                RcppEigen_0.3.3.7.0        
## [179] fgsea_1.12.0                remotes_2.4.2              
## [181] scBFA_1.0.0                 destiny_3.0.1              
## [183] VIM_6.1.1                   testthat_3.1.0             
## [185] htmltools_0.5.2             BiocFileCache_1.10.2       
## [187] yaml_2.2.1                  utf8_1.1.4                 
## [189] plotly_4.9.2.1              XML_3.99-0.3               
## [191] ModelMetrics_1.2.2.2        e1071_1.7-3                
## [193] foreign_0.8-76              withr_2.5.0                
## [195] fitdistrplus_1.0-14         BiocParallel_1.20.1        
## [197] xgboost_1.4.1.1             bit64_4.0.5                
## [199] foreach_1.5.0               robustbase_0.93-9          
## [201] Biostrings_2.54.0           GOSemSim_2.13.1            
## [203] rsvd_1.0.3                  memoise_2.0.0              
## [205] evaluate_0.18               forcats_0.5.0              
## [207] rio_0.5.16                  geneplotter_1.64.0         
## [209] tzdb_0.1.2                  caret_6.0-86               
## [211] ps_1.6.0                    DiagrammeR_1.0.6.1         
## [213] curl_4.3                    fdrtool_1.2.15             
## [215] fansi_0.4.1                 highr_0.8                  
## [217] urltools_1.7.3              xts_0.12.1                 
## [219] GSEABase_1.48.0             acepack_1.4.1              
## [221] edgeR_3.28.1                checkmate_2.0.0            
## [223] scds_1.2.0                  cachem_1.0.6               
## [225] npsurv_0.4-0                babelgene_22.3             
## [227] rjson_0.2.20                openxlsx_4.1.5             
## [229] ggrepel_0.9.1               clue_0.3-60                
## [231] rprojroot_2.0.2             stabledist_0.7-1           
## [233] tools_3.6.3                 sass_0.4.0                 
## [235] nichenetr_1.1.1             magrittr_2.0.1             
## [237] RCurl_1.98-1.2              proxy_0.4-24               
## [239] car_3.0-11                  ape_5.3                    
## [241] ggplotify_0.0.5             xml2_1.3.2                 
## [243] httr_1.4.2                  assertthat_0.2.1           
## [245] rmarkdown_2.18              boot_1.3-25                
## [247] globals_0.14.0              R6_2.4.1                   
## [249] Rhdf5lib_1.8.0              nnet_7.3-14                
## [251] RcppHNSW_0.2.0              progress_1.2.2             
## [253] genefilter_1.68.0           statmod_1.4.34             
## [255] gtools_3.8.2                shape_1.4.6                
## [257] HDF5Array_1.14.4            BiocSingular_1.2.2         
## [259] rhdf5_2.30.1                splines_3.6.3              
## [261] AUCell_1.8.0                carData_3.0-4              
## [263] colorspace_1.4-1            generics_0.1.0             
## [265] stats4_3.6.3                base64enc_0.1-3            
## [267] dynfeature_1.0.0            smoother_1.1               
## [269] gridtext_0.1.1              pillar_1.6.3               
## [271] tweenr_1.0.1                sp_1.4-1                   
## [273] ggplot.multistats_1.0.0     rvcheck_0.1.8              
## [275] GenomeInfoDbData_1.2.2      plyr_1.8.6                 
## [277] gtable_0.3.0                zip_2.2.0                  
## [279] knitr_1.41                  latticeExtra_0.6-29        
## [281] biomaRt_2.42.1              IRanges_2.20.2             
## [283] fastmap_1.1.0               ADGofTest_0.3              
## [285] copula_1.0-0                doParallel_1.0.15          
## [287] AnnotationDbi_1.48.0        vcd_1.4-8                  
## [289] babelwhale_1.0.1            openssl_1.4.1              
## [291] scales_1.1.1                backports_1.2.1            
## [293] S4Vectors_0.24.4            ipred_0.9-12               
## [295] enrichplot_1.6.1            hms_1.1.1                  
## [297] ggforce_0.3.1               Rtsne_0.15                 
## [299] shiny_1.7.1                 numDeriv_2016.8-1.1        
## [301] polyclip_1.10-0             lazyeval_0.2.2             
## [303] Formula_1.2-3               tsne_0.1-3                 
## [305] crayon_1.3.4                MASS_7.3-54                
## [307] pROC_1.16.2                 viridis_0.5.1              
## [309] dynparam_1.0.0              rpart_4.1-15               
## [311] zinbwave_1.8.0              compiler_3.6.3             
## [313] ggtext_0.1.0
LS0tCnRpdGxlOiAiSFMgcHJvamVjdCIKc3VidGl0bGU6ICJab29tIGluIE9SUyBhbmQgSUZFIGJhc2FsIGNlbGxzIgphdXRob3I6ICJBdWRyZXkiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVZLSVtLSVkJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKLS0tCgo8c3R5bGU+CmJvZHkgewp0ZXh0LWFsaWduOiBqdXN0aWZ5fQo8L3N0eWxlPgoKPCEtLSBBdXRvbWF0aWNhbGx5IGNvbXB1dGVzIGFuZCBwcmludHMgaW4gdGhlIG91dHB1dCB0aGUgcnVubmluZyB0aW1lIGZvciBhbnkgY29kZSBjaHVuayAtLT4KYGBge3IsIGVjaG89RkFMU0V9CiMgaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vcm1hcmtkb3duL2lzc3Vlcy8xNDUzCmhvb2tzID0ga25pdHI6OmtuaXRfaG9va3MkZ2V0KCkKaG9va19mb2xkYWJsZSA9IGZ1bmN0aW9uKHR5cGUpIHsKICBmb3JjZSh0eXBlKQogIGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgIHJlcyA9IGhvb2tzW1t0eXBlXV0oeCwgb3B0aW9ucykKICAgIAogICAgaWYgKGlzRkFMU0Uob3B0aW9uc1tbcGFzdGUwKCJmb2xkXyIsIHR5cGUpXV0pKSByZXR1cm4ocmVzKQogICAgCiAgICBwYXN0ZTAoCiAgICAgICI8ZGV0YWlscz48c3VtbWFyeT4iLCAic2hvdyIsICI8L3N1bW1hcnk+XG5cbiIsCiAgICAgIHJlcywKICAgICAgIlxuXG48L2RldGFpbHM+IgogICAgKQogIH0KfQprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgb3V0cHV0ID0gaG9va19mb2xkYWJsZSgib3V0cHV0IiksCiAgcGxvdCA9IGhvb2tfZm9sZGFibGUoInBsb3QiKSwKICB0aW1lX2l0ID0gbG9jYWwoewogICAgbm93ID0gTlVMTAogICAgZnVuY3Rpb24oYmVmb3JlLCBvcHRpb25zKSB7CiAgICAgIGlmIChvcHRpb25zJHRpbWVfaXQpIHsKICAgICAgICBpZiAoYmVmb3JlKSB7CiAgICAgICAgICBub3cgPD0gU3lzLnRpbWUoKQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICByZXMgPSBkaWZmdGltZShTeXMudGltZSgpLCBub3csIHVuaXRzID0gInNlY3MiKQogICAgICAgICAgcGFzdGUoIihUaW1lIHRvIHJ1biA6Iiwgcm91bmQocmVzLCBkaWdpdHMgPSAyKSwgInMpIikKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9KQopCmBgYAoKPCEtLSBTZXQgZGVmYXVsdCBwYXJhbWV0ZXJzIGZvciBhbGwgY2h1bmtzIC0tPgpgYGB7ciwgc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kc2V0LnNlZWQoMTMzN0wpCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgIyBkaXNwbGF5IGNvZGUKICAgICAgICAgICAgICAgICAgICAgICMgZGlzcGxheSBjaHVuayBvdXRwdXQKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZvbGRfb3V0cHV0ID0gRkFMU0UsICMgdXNlZnVsbCBmb3Igc2Vzc2lvbkluZm8oKQogICAgICAgICAgICAgICAgICAgICAgZm9sZF9wbG90ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICMgZmlndXJlIHNldHRpbmdzCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAnY2VudGVyJywKICAgICAgICAgICAgICAgICAgICAgIGZpZy53aWR0aCA9IDIwLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDE1LAogICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAjIHNvbWV0aGluZyBhYm91dCBzZWVkLCBjaHVuayBhbmQgUm1hcmtkb3duIGNvbXBpbGF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzM5NDE3MDAzL2xvbmctdmVjdG9ycy1ub3Qtc3VwcG9ydGVkLXlldC1lcnJvci1pbi1ybWQtYnV0LW5vdC1pbi1yLXNjcmlwdAogICAgICAgICAgICAgICAgICAgICAgIyBjYWNoZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5sYXp5ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAjIGFkZCBydW50aW1lIGFmdGVyIGNodW5rCiAgICAgICAgICAgICAgICAgICAgICB0aW1lX2l0ID0gRkFMU0UpCmBgYAoKClRoaXMgZmlsZSBpcyB1c2VkIHRvIGFuYWx5c2UgdGhlIE9SUyArIElGRSBiYXNhbCBkYXRhc2V0LgoKYGBge3IgbGlicmFyeX0KbGlicmFyeShkcGx5cikKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKCi5saWJQYXRocygpCmBgYAoKIyBQcmVwYXJhdGlvbgoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBzZXQgdGhlIGdsb2JhbCBzZXR0aW5ncyBvZiB0aGUgYW5hbHlzaXMuIFdlIHdpbGwgc3RvcmUgZGF0YSB0aGVyZSA6CgpgYGB7ciBvdXRfZGlyfQpzYXZlX25hbWUgPSAib3JzX2lmZWIiCm91dF9kaXIgPSAiLiIKYGBgCgpXZSBsb2FkIHRoZSBkYXRhc2V0IDoKCmBgYHtyIGxvYWRfc29ian0Kc29iaiA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvIiwgc2F2ZV9uYW1lLCAiX3NvYmoucmRzIikpCnNvYmoKYGBgCgpXZSBsb2FkIHRoZSBzYW1wbGUgaW5mb3JtYXRpb24gOgoKYGBge3IgY3VzdG9tX3BhbGV0dGVfc2FtcGxlLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gNn0Kc2FtcGxlX2luZm8gPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLy4uLy4uLzFfbWV0YWRhdGEvaHNfaGRfc2FtcGxlX2luZm8ucmRzIikpCnByb2plY3RfbmFtZXNfb2kgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUKCmdyYXBoaWNzOjpwaWUocmVwKDEsIG5yb3coc2FtcGxlX2luZm8pKSwKICAgICAgICAgICAgICBjb2wgPSBzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICBsYWJlbHMgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpCmBgYAoKSGVyZSBhcmUgY3VzdG9tIGNvbG9ycyBmb3IgZWFjaCBjZWxsIHR5cGUgOgoKYGBge3IgY29sb3JfbWFya2VycywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxLjIsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpjb2xvcl9tYXJrZXJzID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8uLi8xX21ldGFkYXRhL2hzX2hkX2NvbG9yX21hcmtlcnMucmRzIikpCgpkYXRhLmZyYW1lKGNlbGxfdHlwZSA9IG5hbWVzKGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgIGNvbG9yID0gdW5saXN0KGNvbG9yX21hcmtlcnMpKSAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoLiwgYWVzKHggPSBjZWxsX3R5cGUsIHkgPSAwLCBmaWxsID0gY2VsbF90eXBlKSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQocGNoID0gMjEsIHNpemUgPSA1KSArCiAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdW5saXN0KGNvbG9yX21hcmtlcnMpLCBicmVha3MgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCwgaGp1c3QgPSAxKSkKYGBgCgpUaGlzIGlzIHRoZSBwcm9qZWN0aW9uIG9mIGludGVyZXN0IDoKCmBgYHtyIG5hbWUyRH0KbmFtZTJEID0gImhhcm1vbnlfMjBfdHNuZSIKYGBgCgpXZSBkZXNpZ24gYSBjdXN0b20gZnVuY3Rpb24gdG8gbWFrZSB0aGUgR1NFQSBwbG90IGFuZCBhIHdvcmQgY2xvdWQgZ3JhcGggOgoKYGBge3IgbWFrZV9nc2VhX3Bsb3QsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQptYWtlX2dzZWFfcGxvdCA9IGZ1bmN0aW9uKGdzZWFfcmVzdWx0cywgZ3Nfb2ksIGZvbGRfY2hhbmdlLCBtZXRyaWMgPSAiRkMiKSB7CiAgZm9sZF9jaGFuZ2UkbWV0cmljID0gZm9sZF9jaGFuZ2VbLCBtZXRyaWNdCiAgCiAgcGxvdF9saXN0ID0gbGFwcGx5KGdzX29pLCBGVU4gPSBmdW5jdGlvbihnZW5lX3NldCkgewogICAgIyBHZW5lIHNldCBjb250ZW50CiAgICBnc19jb250ZW50ID0gZ2VuZV9zZXRzICU+JQogICAgICBkcGx5cjo6ZmlsdGVyKGdzX25hbWUgPT0gZ2VuZV9zZXQpICU+JQogICAgICBkcGx5cjo6cHVsbChlbnNlbWJsX2dlbmUpICU+JQogICAgICB1bmlxdWUoKQogICAgCiAgICAjIEdlbmUgc2V0IHNpemUKICAgIG5iX2dlbmVzID0gbGVuZ3RoKGdzX2NvbnRlbnQpCiAgICAKICAgICMgRW5yaWNobWVudCBtZXRyaWNzCiAgICBORVMgPSBnc2VhX3Jlc3VsdHNAcmVzdWx0W2dlbmVfc2V0LCAiTkVTIl0KICAgIHAuYWRqdXN0ID0gZ3NlYV9yZXN1bHRzQHJlc3VsdFtnZW5lX3NldCwgInAuYWRqdXN0Il0gJT4lCiAgICAgIHJvdW5kKC4sIDQpCiAgICBxdmFsdWVzID0gZ3NlYV9yZXN1bHRzQHJlc3VsdFtnZW5lX3NldCwgInF2YWx1ZXMiXQogICAgCiAgICBpZiAocC5hZGp1c3QgPiAwLjA1KSB7CiAgICAgIHAuYWRqdXN0ID0gcGFzdGUwKCI8c3BhbiBzdHlsZT0nY29sb3I6cmVkOyc+IiwgcC5hZGp1c3QsICI8L3NwYW4+IikKICAgIH0KICAgIAogICAgbXlfc3VidGl0bGUgPSBwYXN0ZTAoIlxuTkVTIDogIiwgcm91bmQoTkVTLCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICIgfCBwYWRqIDogIiwgcC5hZGp1c3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAiIHwgcXZhbCA6ICIsIHJvdW5kKHF2YWx1ZXMsIDQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIiB8IHNldCBzaXplIDogIiwgbmJfZ2VuZXMsICIgZ2VuZXMiKQogICAgCiAgICAjIFNpemUgbGltaXRzCiAgICBsb3dlcl9GQyA9IG1pbihmb2xkX2NoYW5nZVtnc19jb250ZW50LCBdJG1ldHJpYywgbmEucm0gPSBUUlVFKQogICAgdXBwZXJfRkMgPSBtYXgoZm9sZF9jaGFuZ2VbZ3NfY29udGVudCwgXSRtZXRyaWMsIG5hLnJtID0gVFJVRSkKICAgIAogICAgIyBQbG90CiAgICBwID0gZW5yaWNocGxvdDo6Z3NlYXBsb3QyKHggPSBnc2VhX3Jlc3VsdHMsIGdlbmVTZXRJRCA9IGdlbmVfc2V0KSArCiAgICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSBnZW5lX3NldCwKICAgICAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9IG15X3N1YnRpdGxlKSArCiAgICAgIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKDMsIDMsIDUsIDMpKSwKICAgICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGdndGV4dDo6ZWxlbWVudF9tYXJrZG93bihoanVzdCA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTApKQogICAgCiAgICB3YyA9IGdncGxvdDI6OmdncGxvdChmb2xkX2NoYW5nZVtnc19jb250ZW50LCBdLAogICAgICAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gZ2VuZV9uYW1lLCBzaXplID0gYWJzKG1ldHJpYyksIGNvbG9yID0gbWV0cmljKSkgKwogICAgICBnZ3dvcmRjbG91ZDo6Z2VvbV90ZXh0X3dvcmRjbG91ZF9hcmVhKHNob3cubGVnZW5kID0gVFJVRSkgKwogICAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudDIoCiAgICAgICAgbmFtZSA9IG1ldHJpYywKICAgICAgICBsb3cgPSBhcXVhcml1czo6Y29sb3JfY252WzFdLAogICAgICAgIG1pZCA9ICJncmF5NzAiLCBtaWRwb2ludCA9IDAsCiAgICAgICAgaGlnaCA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbM10pICsKICAgICAgZ2dwbG90Mjo6c2NhbGVfc2l6ZV9hcmVhKG1heF9zaXplID0gNykgKwogICAgICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKwogICAgICBnZ3Bsb3QyOjpndWlkZXMoc2l6ZSA9ICJub25lIikKICAgIAogICAgcmV0dXJuKGxpc3QocCwgd2MpKQogIH0pICU+JSB1bmxpc3QoLiwgcmVjdXJzaXZlID0gRkFMU0UpCiAgCiAgcmV0dXJuKHBsb3RfbGlzdCkKfQpgYGAKCgojIFZpc3VhbGl6YXRpb24KCiMjIEdlbmUgZXhwcmVzc2lvbgoKV2UgdmlzdWFsaXplIGdlbmUgZXhwcmVzc2lvbiBmb3Igc29tZSBtYXJrZXJzIDoKCmBgYHtyIHBsb3RfbGlzdF9mZWF0dXJlcywgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA0fQpmZWF0dXJlcyA9IGMoInBlcmNlbnQubXQiLCAicGVyY2VudC5yYiIsICJuRmVhdHVyZV9STkEiKQoKcGxvdF9saXN0ID0gbGFwcGx5KGZlYXR1cmVzLCBGVU4gPSBmdW5jdGlvbihvbmVfZ2VuZSkgewogIFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSBvbmVfZ2VuZSwKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGFxdWFyaXVzOjpjb2xvcl9nZW5lKSArCiAgICBTZXVyYXQ6Ok5vQXhlcygpCn0pCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gMykKYGBgCgojIyBDbHVzdGVycwoKV2UgdmlzdWFsaXplIGNsdXN0ZXJzIDoKCmBgYHtyIHNlZV9jbHVzdGVyaW5nLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gNH0KY2x1c3Rlcl9wbG90ID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwgbGFiZWwgPSBUUlVFKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSkKY2x1c3Rlcl9wbG90CmBgYAoKIyMgQ2VsbCB0eXBlCgpXZSB2aXN1YWxpemUgY2VsbCB0eXBlIHNwbGl0IGJ5IHNhbXBsZSA6CgpgYGB7ciBwbG90X3NwbGl0X2RpbXJlZCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA3fQpwbG90X2xpc3QgPSBhcXVhcml1czo6cGxvdF9zcGxpdF9kaW1yZWQoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0X2J5ID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRfY29sb3IgPSBzZXROYW1lcyhzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm0gPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfY29sb3IgPSBjb2xvcl9tYXJrZXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmdfcHRfc2l6ZSA9IDAuNSwgbWFpbl9wdF9zaXplID0gMC41KQoKcGxvdF9saXN0W1tsZW5ndGgocGxvdF9saXN0KSArIDFdXSA9IGNsdXN0ZXJfcGxvdAoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpICYKICBTZXVyYXQ6Ok5vTGVnZW5kKCkKYGBgCgojIyBDbHVzdGVyIHR5cGUKCldlIHN1bW1hcml6ZSBtYWpvciBjZWxsIHR5cGUgYnkgY2x1c3RlciA6CgpgYGB7ciBjZWxsX3R5cGVfY2x1c3RlcnN9CmNlbGxfdHlwZV9jbHVzdGVycyA9IHNvYmpAbWV0YS5kYXRhWywgYygiY2VsbF90eXBlIiwgInNldXJhdF9jbHVzdGVycyIpXSAlPiUKICB0YWJsZSgpICU+JQogIHByb3AudGFibGUoLiwgbWFyZ2luID0gMikgJT4lCiAgYXBwbHkoLiwgMiwgd2hpY2gubWF4KQpjZWxsX3R5cGVfY2x1c3RlcnMgPSBzZXROYW1lcyhsZXZlbHMoc29iaiRjZWxsX3R5cGUpW2NlbGxfdHlwZV9jbHVzdGVyc10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5tID0gbmFtZXMoY2VsbF90eXBlX2NsdXN0ZXJzKSkKCmBgYAoKV2UgZGVmaW5lIGNsdXN0ZXIgdHlwZSA6CgpgYGB7ciB0YWJsZV9jbHVzdGVyX3R5cGV9CnNvYmokY2x1c3Rlcl90eXBlID0gY2VsbF90eXBlX2NsdXN0ZXJzW3NvYmokc2V1cmF0X2NsdXN0ZXJzXSAlPiUKICBhcy5mYWN0b3IoKQp0YWJsZShzb2JqJGNsdXN0ZXJfdHlwZSwgc29iaiRjZWxsX3R5cGUpCmBgYAoKV2Ugc3Vic2V0IGBjb2xvcl9tYXJrZXJzYCA6CgpgYGB7ciBzdWJzZXRfY29sb3JzfQpjb2xvcl9tYXJrZXJzID0gY29sb3JfbWFya2Vyc1tsZXZlbHMoc29iaiRjbHVzdGVyX3R5cGUpXQpgYGAKCgpXZSBjb21wYXJlIGNsdXN0ZXIgYW5ub3RhdGlvbiBhbmQgY2VsbCB0eXBlIGFubm90YXRpb24gOgoKYGBge3Igc2VlX2NsdXN0ZXJfdHlwZSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQpwMSA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsIGNvbHMgPSBjb2xvcl9tYXJrZXJzKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDZWxsIHR5cGUiKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnAyID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImNsdXN0ZXJfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwgY29scyA9IGNvbG9yX21hcmtlcnMpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkNsdXN0ZXIgdHlwZSIpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgZ3VpZGVzID0gImNvbGxlY3QiKQpgYGAKCiMjIEJhcnBsb3QKCldlIG1ha2UgYSBiYXJwbG90IHRvIGNvbXBhcmUgT1JTIGFuZCBJRkUgYmFzYWwgcG9wdWxhdGlvbnMgaW4gSFMgdnMgSEQgc2FtcGxlcy4gVGhlIHByb3BvcnRpb24gb2YgT1JTIGlzIGluZGljYXRlZCBvbiB0b3Agb2YgYmFycy4KCmBgYHtyIGJhcnBsb3RfaWJsX29ycywgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDV9CnF1YW50aWYgPSB0YWJsZShzb2JqJHNhbXBsZV9pZGVudGlmaWVyLAogICAgICAgICAgICAgICAgc29iaiRjbHVzdGVyX3R5cGUpICU+JQogIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUKICBgY29sbmFtZXM8LWAoYygiU2FtcGxlIiwgImNlbGxfdHlwZSIsICJuYl9jZWxscyIpKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoU2FtcGxlKSAlPiUKICBkcGx5cjo6bXV0YXRlKHRvdGFsX2NlbGxzID0gc3VtKG5iX2NlbGxzKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIGRwbHlyOjpmaWx0ZXIoY2VsbF90eXBlID09ICJPUlMiKSAlPiUKICBkcGx5cjo6bXV0YXRlKHByb3BfaWJsID0gbmJfY2VsbHMgLyB0b3RhbF9jZWxscykgJT4lCiAgZHBseXI6Om11dGF0ZShwcm9wX2libCA9IDEwMCpyb3VuZChwcm9wX2libCwgNCkpCgpzb2JqJHNldXJhdF9jbHVzdGVycyA9IGZhY3Rvcihzb2JqJHNldXJhdF9jbHVzdGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gbmFtZXMoc29ydChjZWxsX3R5cGVfY2x1c3RlcnMpKSkKCmFxdWFyaXVzOjpwbG90X2JhcnBsb3QoZGYgPSB0YWJsZShzb2JqJHNhbXBsZV9pZGVudGlmaWVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc29iaiRzZXVyYXRfY2x1c3RlcnMpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZS50YWJsZSgpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYGNvbG5hbWVzPC1gKGMoInNhbXBsZV9pZGVudGlmaWVyIiwgImNsdXN0ZXJzIiwgIm5iX2NlbGxzIikpLAogICAgICAgICAgICAgICAgICAgICAgIHggPSAic2FtcGxlX2lkZW50aWZpZXIiLCB5ID0gIm5iX2NlbGxzIiwgZmlsbCA9ICJjbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKCkpICsKICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yUmFtcFBhbGV0dGUoYygicm95YWxibHVlMSIsICJyb3lhbGJsdWU0IikpKHRhYmxlKHNvcnQoY2VsbF90eXBlX2NsdXN0ZXJzKSlbIklGRSBiYXNhbCJdKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yUmFtcFBhbGV0dGUoYygiY2hhcnRyZXVzZTEiLCAiY2hhcnRyZXVzZTQiKSkodGFibGUoc29ydChjZWxsX3R5cGVfY2x1c3RlcnMpKVsiT1JTIl0pKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhzb3J0KGNlbGxfdHlwZV9jbHVzdGVycykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ2VsbCB0eXBlIikgKwogIGdncGxvdDI6Omdlb21fbGFiZWwoZGF0YSA9IHF1YW50aWYsIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IC5kYXRhJFNhbXBsZSwgeSA9IDEuMDUsIGxhYmVsID0gLmRhdGEkcHJvcF9pYmwpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAsIHNpemUgPSA1KQpgYGAKCiMgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24KCkluIHRoaXMgc2VjdGlvbiwgd2UgcGVyZm9ybSBERSBiZXR3ZWVuIDoKCi0gaW5uZXIgYnVsZ2UgbGF5ZXIgKE9SUykgYW5kIG91dGVyIHJvb3Qgc2hlYXRoIChJRkUgYmFzYWwpIHBvcHVsYXRpb25zCi0gaGVhbHRoeSBkb25vcnMgKEhEKSBhbmQgSFMgcGF0aWVudHMgKEhTKSB3aXRoaW4gSUZFIGJhc2FsIHBvcHVsYXRpb24KLSBoZWFsdGh5IGRvbm9ycyAoSEQpIGFuZCBIUyBwYXRpZW50cyAoSFMpIHdpdGhpbiBPUlMgcG9wdWxhdGlvbgotIGNsdXN0ZXIgNCB2cyByZXN0IG9mIElGRSBiYXNhbCwgYmVjYXVzZSB0aGlzIGNsdXN0ZXIgaXMgc3BlY2lmaWMgdG8gSFMgY29tcGFyZWQgdG8gSEQKCldlIHNhdmUgdGhlIHJlc3VsdHMgaW4gYSBsaXN0IDoKCmBgYHtyIGxpc3RfcmVzdWx0c30KbGlzdF9yZXN1bHRzID0gbGlzdCgpCmBgYAoKV2UgbWFrZSBvdmVyLXJlcHJlc2VudGF0aW9uIGFuYWx5c2lzIGZvciBlYWNoIGdyb3VwIG9mIGdlbmVzLiBXZSBsb2FkIGdlbmUgc2V0cyBmcm9tIE1TaWdEQiA6CgpgYGB7ciBnZW5lX3NldHN9CmdlbmVfc2V0cyA9IGFxdWFyaXVzOjpnZXRfZ2VuZV9zZXRzKHNwZWNpZXMgPSAiSG9tbyBzYXBpZW5zIikKZ2VuZV9zZXRzID0gZ2VuZV9zZXRzJGdlbmVfc2V0cwoKaGVhZChnZW5lX3NldHMpCmBgYAoKSG93IG1hbnkgZ2VuZSBzZXRzID8KCmBgYHtyIGNvdW50X2dlbmVfc2V0c30KZ2VuZV9zZXRzWywgYygiZ3Nfc3ViY2F0IiwgImdzX25hbWUiKV0gJT4lCiAgZHBseXI6OmRpc3RpbmN0KCkgJT4lCiAgZHBseXI6OnB1bGwoZ3Nfc3ViY2F0KSAlPiUKICB0YWJsZSgpICU+JQogIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUKICBgY29sbmFtZXM8LWAoYygiQ2F0ZWdvcnkiLCAiTmIgZ2VuZSBzZXRzIikpCmBgYAoKV2UgZ2V0IGdlbmUgbmFtZSBhbmQgZ2VuZSBJRCBjb3JyZXNwb25kZW5jZSA6CgpgYGB7ciBnZW5lX2NvcnJlc3B9CmdlbmVfY29ycmVzcCA9IHNvYmpAYXNzYXlzW1siUk5BIl1dQG1ldGEuZmVhdHVyZXNbLCBjKCJnZW5lX25hbWUiLCAiRW5zZW1ibF9JRCIpXSAlPiUKICBgY29sbmFtZXM8LWAoYygiTkFNRSIsICJJRCIpKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gYXMuY2hhcmFjdGVyKElEKSkKcm93bmFtZXMoZ2VuZV9jb3JyZXNwKSA9IGdlbmVfY29ycmVzcCRJRAoKaGVhZChnZW5lX2NvcnJlc3ApCmBgYAoKCiMjIE9SUyB2cyBJRkUgYmFzYWwKCmBgYHtyIGdyb3VwX25hbWVfaWJsX29yc30KZ3JvdXBfbmFtZSA9ICJPUlNfdnNfSUZFX2Jhc2FsIgpgYGAKCgpXZSBjaGFuZ2UgY2VsbCBpZGVudGl0aWVzIHRvIGNsdXN0ZXIgdHlwZSA6CgpgYGB7ciBpZGVudF9jbHVzdGVyX3R5cGV9ClNldXJhdDo6SWRlbnRzKHNvYmopID0gc29iaiRjbHVzdGVyX3R5cGUKCnRhYmxlKFNldXJhdDo6SWRlbnRzKHNvYmopLCBzb2JqJHNhbXBsZV90eXBlKQpgYGAKCiMjIyBERQoKV2UgaWRlbnRpZnkgc3BlY2lmaWMgbWFya2VycyBmb3IgZWFjaCBwb3B1bGF0aW9uIDoKCmBgYHtyIGRlX2NsdXN0MCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9Cm1hcmsgPSBTZXVyYXQ6OkZpbmRNYXJrZXJzKHNvYmosIGlkZW50LjEgPSAiT1JTIiwgaWRlbnQuMiA9ICJJRkUgYmFzYWwiKQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1tncm91cF9uYW1lXV0kbWFyayA9IG1hcmsKCmRpbShtYXJrKQpoZWFkKG1hcmssIG4gPSAyMCkKYGBgCgpUaGVyZSBhcmUgYHIgbnJvdyhtYXJrKWAgZ2VuZXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkLiBIb3cgbWFueSBmb3IgZWFjaCBwb3B1bGF0aW9uID8KCmBgYHtyIG1ha3JfYnlfcG9wfQojIE9SUwptYXJrX2libCA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihhdmdfbG9nRkMgPiAwKQpucm93KG1hcmtfaWJsKQoKIyBJRkUgYmFzYWwKbWFya19vcnMgPSBtYXJrICU+JQogIGRwbHlyOjpmaWx0ZXIoYXZnX2xvZ0ZDIDwgMCkKbnJvdyhtYXJrX29ycykKYGBgCgpXZSByZXByZXNlbnQgdGhlIGluZm9ybWF0aW9uIG9uIGEgZmlndXJlIDoKCmBgYHtyIHBsb3RfZGVfcG9wLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDEwfQptYXJrJGdlbmVfbmFtZSA9IHJvd25hbWVzKG1hcmspCm1hcmtfdG9fbGFiZWwgPSByYmluZCgKICAjIHVwLXJlZ3VsYXRlZCBpbiBPUlMKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IGF2Z19sb2dGQyksCiAgIyB1cC1yZWd1bGF0ZWQgaW4gSUZFIGJhc2FsCiAgbWFyayAlPiUgZHBseXI6OnRvcF9uKC4sIG4gPSAyMCwgd3QgPSAtYXZnX2xvZ0ZDKSwKICAjIHJlcHJlc2VudGF0aXZlIGFuZCBzZWxlY3RpdmUgZm9yIE9SUwogIG1hcmsgJT4lIGRwbHlyOjp0b3BfbiguLCBuID0gMjAsIHd0ID0gKHBjdC4xIC0gcGN0LjIpKSwKICAjIHJlcHJlc2VudGF0aXZlIGFuZCBzZWxlY3RpdmUgZm9yIElGRSBiYXNhbAogIG1hcmsgJT4lIGRwbHlyOjp0b3BfbiguLCBuID0gMjAsIHd0ID0gLShwY3QuMSAtIHBjdC4yKSkpICU+JQogIGRwbHlyOjpkaXN0aW5jdCgpCgpnZ3Bsb3QyOjpnZ3Bsb3QobWFyaywgYWVzKHggPSBwY3QuMSwgeSA9IHBjdC4yLCBjb2wgPSBhdmdfbG9nRkMpKSArCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBsdHkgPSAyKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludCgpICsKICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSBtYXJrX3RvX2xhYmVsLCBtYXgub3ZlcmxhcHMgPSBJbmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHBjdC4xLCB5ID0gcGN0LjIsIGxhYmVsID0gZ2VuZV9uYW1lKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbCA9ICJibGFjayIsIGZpbGwgPSBOQSwgc2l6ZSA9IDMsIGxhYmVsLnNpemUgPSBOQSkgKwogIGdncGxvdDI6OmxhYnModGl0bGUgPSAiRGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIiwKICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gImJldHdlZW4gT1JTIGFuZCBJRkUgYmFzYWwiKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZCA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSBhcXVhcml1czo6Y29sb3JfY252WzNdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDApICsKICBnZ3Bsb3QyOjp0aGVtZV9jbGFzc2ljKCkgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCldlIHJlcHJlc2VudCB0aGUgYmVzdCBnZW5lcyBvbiBhIHZpb2xpbiBwbG90LCBncm91cCBieSBjbHVzdGVyIHR5cGUgYW5kIHNwbGl0IGJ5IHNhbXBsZSB0eXBlIDoKCmBgYHtyIHZpb2xpbl9kZV9wb3AsIGZpZy53aWR0aCA9IDE1LCBmaWcuaGVpZ2h0ID0gMzl9Cm1hcmtfdG9fbGFiZWwgPSBtYXJrX3RvX2xhYmVsICU+JQogIGRwbHlyOjphcnJhbmdlKHBjdC4xIC0gcGN0LjIpCgpwbG90X2xpc3QgPSBsYXBwbHkobWFya190b19sYWJlbCRnZW5lX25hbWUsIEZVTiA9IGZ1bmN0aW9uKGdlbmUpIHsKICBwID0gU2V1cmF0OjpWbG5QbG90KHNvYmosIGZlYXR1cmVzID0gZ2VuZSwgcHQuc2l6ZSA9IDAuMDAxLAogICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiY2x1c3Rlcl90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgIHNwbGl0LmJ5ID0gInNhbXBsZV90eXBlIikgKwogICAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwoYnJlYWtzID0gYygiSFMiLCAiSEQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoIiNDNTVGNDAiLCAiIzJDNzhFNiIpKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgcmV0dXJuKHApCn0pCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gNSkKYGBgCgpPbiB0aGUgZmlndXJlLCB3ZSBjYW4gc2VlIHRoYXQgc29tZSBzcGVjaWZpYyBtYXJrZXJzIGZvciBhIHBvcHVsYXRpb24gYXJlIGluZGVlZCBzcGVjaWZpYyB0byBhIHNhbXBsZSB0eXBlIHJhdGhlciB0aGFuIHRoZSB3aG9sZSBwb3B1bGF0aW9uLgoKIyMjIE9SUwoKV2UgZXhwbG9yZSBlbnJpY2htZW50IGluIGdlbmUgc2V0cyBmb3IgT1JTIHBvcHVsYXRpb24KCmBgYHtyIG9yYV9pYmwsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNn0KZ2VuZXNfb2ZfaW50ZXJlc3QgPSByb3duYW1lcyhtYXJrX2libCkKCmVucmljaHJfcmVzdWx0cyA9IGFxdWFyaXVzOjpydW5fZW5yaWNocihnZW5lX25hbWVzID0gZ2VuZXNfb2ZfaW50ZXJlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2NvcnJlc3AgPSBnZW5lX2NvcnJlc3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3NldHMgPSBnZW5lX3NldHNbLCBjKCJnc19uYW1lIiwgImVuc2VtYmxfZ2VuZSIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ha2VfcGxvdCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIlVwLXJlZ3VsYXRlZCBpbiBPUlMgY29tcGFyZWQgdG8gSUZFIGJhc2FsIikKCmxpc3RfcmVzdWx0c1tbZ3JvdXBfbmFtZV1dJGVucmljaHJfaWJsID0gZW5yaWNocl9yZXN1bHRzJGVnbwoKZW5yaWNocl9yZXN1bHRzJHBsb3QgKwogIGdncGxvdDI6OnRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkKYGBgCgojIyMgSUZFIGJhc2FsCgpXZSBleHBsb3JlIGVucmljaG1lbnQgaW4gZ2VuZSBzZXRzIGZvciBJRkUgYmFzYWwgcG9wdWxhdGlvbi4KCmBgYHtyIG9yYV9vcnMsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNn0KZ2VuZXNfb2ZfaW50ZXJlc3QgPSByb3duYW1lcyhtYXJrX29ycykKCmVucmljaHJfcmVzdWx0cyA9IGFxdWFyaXVzOjpydW5fZW5yaWNocihnZW5lX25hbWVzID0gZ2VuZXNfb2ZfaW50ZXJlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2NvcnJlc3AgPSBnZW5lX2NvcnJlc3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3NldHMgPSBnZW5lX3NldHNbLCBjKCJnc19uYW1lIiwgImVuc2VtYmxfZ2VuZSIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ha2VfcGxvdCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIlVwLXJlZ3VsYXRlZCBpbiBJRkUgYmFzYWwgY29tcGFyZWQgdG8gT1JTIikKCmxpc3RfcmVzdWx0c1tbZ3JvdXBfbmFtZV1dJGVucmljaHJfb3JzID0gZW5yaWNocl9yZXN1bHRzJGVnbwoKZW5yaWNocl9yZXN1bHRzJHBsb3QgKwogIGdncGxvdDI6OnRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkKYGBgCgoKIyMjIEdTRUEKCldlIHJ1biBhIEdTRUEgZm9yIGFsbCBnZW5lIHNldHMsIGZyb20gdGhlIGZ1bGwgY291bnQgbWF0cml4IDoKCmBgYHtyIGdzZWFfaWJsX29ycywgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA0MH0KcmFua2VkX2dlbmVfbGlzdCA9IGFxdWFyaXVzOjpydW5fZm9sZGNoYW5nZShTZXVyYXQ6OkdldEFzc2F5RGF0YShzb2JqLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImNvdW50cyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwMSA9IGNvbG5hbWVzKHNvYmopW3NvYmpAYWN0aXZlLmlkZW50ICVpbiUgIk9SUyJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwMiA9IGNvbG5hbWVzKHNvYmopW3NvYmpAYWN0aXZlLmlkZW50ICVpbiUgIklGRSBiYXNhbCJdKQpuYW1lcyhyYW5rZWRfZ2VuZV9saXN0KSA9IGdlbmVfY29ycmVzcCRJRAoKZ3NlYV9yZXN1bHRzID0gYXF1YXJpdXM6OmdzZWFfcnVuKHJhbmtlZF9nZW5lX2xpc3QgPSByYW5rZWRfZ2VuZV9saXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHU0VBX3BfdmFsX3RocmVzaCA9IDEpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRnc2VhID0gZ3NlYV9yZXN1bHRzCgpnc2VhX3Jlc3VsdHNAcmVzdWx0ICU+JQogIGRwbHlyOjpmaWx0ZXIocHZhbHVlIDwgMC4wNSkgJT4lCiAgZHBseXI6OnRvcF9uKC4sIG4gPSAyMDAsIHd0ID0gYWJzKE5FUykpICU+JQogIGRwbHlyOjptdXRhdGUodG9vX2xvbmcgPSBpZmVsc2UobmNoYXIoSUQpID4gNjAsIHllcyA9IFRSVUUsIG5vID0gRkFMU0UpKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gc3RyaW5ncjo6c3RyX3N1YihJRCwgZW5kID0gNjApKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gaWZlbHNlKHRvb19sb25nLCB5ZXMgPSBwYXN0ZTAoSUQsICIuLi4iKSwgbm8gPSBJRCkpICU+JQogIGFxdWFyaXVzOjpnc2VhX3Bsb3Qoc2hvd19sZWdlbmQgPSBUUlVFKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJHU0VBIHVzaW5nIGFsbCBnZW5lcyAoY291bnQgbWF0cml4KSIpICsKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpCmBgYAoKV2UgbWFrZSB0aGUgZ3NlYSBwbG90IGZvciB0d28gZ2VuZSBzZXRzIDoKCmBgYHtyIGdzZWFfcGxvdF9pYmxfb3JzLCBmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDd9CnAxID0gZW5yaWNocGxvdDo6Z3NlYXBsb3QyKHggPSBnc2VhX3Jlc3VsdHMsIGdlbmVTZXRJRCA9ICJSRUFDVE9NRV9LRVJBVElOSVpBVElPTiIpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIlJFQUNUT01FX0tFUkFUSU5JWkFUSU9OIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4oMywgMywgNSwgMykpKQoKcDIgPSBlbnJpY2hwbG90Ojpnc2VhcGxvdDIoeCA9IGdzZWFfcmVzdWx0cywgZ2VuZVNldElEID0gIkhBTExNQVJLX0lOVEVSRkVST05fR0FNTUFfUkVTUE9OU0UiKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJIQUxMTUFSS19JTlRFUkZFUk9OX0dBTU1BX1JFU1BPTlNFIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4oMywgMywgNSwgMykpKQoKcDEgfCBwMgpgYGAKCk1heWJlIHRob3NlIGdlbmUgc2V0cyBhcmUgc3BlY2lmaWMgdG8gc2FtcGxlIHR5cGUgcmF0aGVyIHRoYW4gdGhlIHdob2xlIGNlbGwgcG9wdWxhdGlvbi4KCiMjIENsdXN0ZXIgNCB3aXRoaW4gSUZFIGJhc2FsCgpgYGB7ciBncm91cF9uYW1lX2NsdXN0ZXI0fQpncm91cF9uYW1lID0gImNsdXN0ZXI0X3ZzX0lGRSBiYXNhbCIKYGBgCgoKV2Ugc3Vic2V0IHRoZSBTZXVyYXQgb2JqZWN0IGFuZCBjaGFuZ2UgY2VsbCBpZGVudGl0aWVzIHRvIHNhbXBsZSB0eXBlLgoKYGBge3Igc3ViX2NsdXN0ZXI0fQpzdWJzb2JqID0gc3Vic2V0KHNvYmosIGNsdXN0ZXJfdHlwZSA9PSAiSUZFIGJhc2FsIikKc3Vic29iaiRzZXVyYXRfY2x1c3RlcnMgPSBiYXNlOjpkcm9wbGV2ZWxzKHN1YnNvYmokc2V1cmF0X2NsdXN0ZXJzKQpTZXVyYXQ6OklkZW50cyhzdWJzb2JqKSA9IHN1YnNvYmokc2V1cmF0X2NsdXN0ZXJzCgp0YWJsZShzdWJzb2JqJHNldXJhdF9jbHVzdGVycykKYGBgCgojIyMgREUKCldlIGlkZW50aWZ5IHNwZWNpZmljIG1hcmtlcnMgZm9yIGVhY2ggcG9wdWxhdGlvbiA6CgpgYGB7ciBkZV9jbHVzdGVyNCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9Cm1hcmsgPSBTZXVyYXQ6OkZpbmRNYXJrZXJzKHN1YnNvYmosIGlkZW50LjEgPSA0KQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1tncm91cF9uYW1lXV0kbWFyayA9IG1hcmsKCmRpbShtYXJrKQpoZWFkKG1hcmssIG4gPSAyMCkKYGBgCgpUaGVyZSBhcmUgYHIgbnJvdyhtYXJrKWAgZ2VuZXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkLiBIb3cgbWFueSBmb3IgZWFjaCBwb3B1bGF0aW9uID8KCmBgYHtyIG1hcmtfY2x1c3RlcjR9CiMgY2x1c3RlciA0Cm1hcmtfY2x1c3RlcjQgPSBtYXJrICU+JQogIGRwbHlyOjpmaWx0ZXIoYXZnX2xvZ0ZDID4gMCkKbnJvdyhtYXJrX2NsdXN0ZXI0KQoKIyBPdGhlciBJRkUgYmFzYWwKbWFya19ub3Q1ID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKGF2Z19sb2dGQyA8IDApCm5yb3cobWFya19ub3Q1KQpgYGAKCldlIHJlcHJlc2VudCB0aGUgaW5mb3JtYXRpb24gb24gYSBmaWd1cmUgOgoKYGBge3IgcGxvdF9kZV9jbHVzdGVyNCwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KbWFyayRnZW5lX25hbWUgPSByb3duYW1lcyhtYXJrKQptYXJrX3NpZ25pZiA9IHJiaW5kKAogICMgdXAtcmVndWxhdGVkIGluIGNsdXN0ZXIgNAogIG1hcmsgJT4lIGRwbHlyOjp0b3BfbiguLCBuID0gMjAsIHd0ID0gYXZnX2xvZ0ZDKSwKICAjIHVwLXJlZ3VsYXRlZCBpbiBvdGhlciBJRkUgYmFzYWwKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IC1hdmdfbG9nRkMpLAogICMgcmVwcmVzZW50YXRpdmUgYW5kIHNlbGVjdGl2ZSBmb3IgY2x1c3RlciA0CiAgbWFyayAlPiUgZHBseXI6OnRvcF9uKC4sIG4gPSAyMCwgd3QgPSAocGN0LjEgLSBwY3QuMikpLAogICMgcmVwcmVzZW50YXRpdmUgYW5kIHNlbGVjdGl2ZSBmb3Igb3RoZXIgSUZFIGJhc2FsCiAgbWFyayAlPiUgZHBseXI6OnRvcF9uKC4sIG4gPSAyMCwgd3QgPSAtKHBjdC4xIC0gcGN0LjIpKSkgJT4lCiAgZHBseXI6OmRpc3RpbmN0KCkKCmdncGxvdDI6OmdncGxvdChtYXJrLCBhZXMoeCA9IHBjdC4xLCB5ID0gcGN0LjIsIGNvbCA9IGF2Z19sb2dGQykpICsKICBnZ3Bsb3QyOjpnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KCkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IG1hcmtfc2lnbmlmLCBtYXgub3ZlcmxhcHMgPSBJbmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHBjdC4xLCB5ID0gcGN0LjIsIGxhYmVsID0gZ2VuZV9uYW1lKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbCA9ICJibGFjayIsIGZpbGwgPSBOQSwgc2l6ZSA9IDMsIGxhYmVsLnNpemUgPSBOQSkgKwogIGdncGxvdDI6OmxhYnModGl0bGUgPSAiRGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIiwKICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gImJldHdlZW4gY2x1c3RlciA0IGFuZCBvdGhlciBJRkUgYmFzYWwiKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZCA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSBhcXVhcml1czo6Y29sb3JfY252WzNdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDApICsKICBnZ3Bsb3QyOjp0aGVtZV9jbGFzc2ljKCkgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCiMjIyBIZWF0bWFwCgpXZSByZXByZXNlbnQgYSBzdWJzZXQgb2YgZ2VuZXMgb24gYSBoZWF0bWFwIDoKCmBgYHtyIHByZXBfZ2VuZXNfY2x1c3RlcjR9CmZlYXR1cmVzX29pID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKHBfdmFsX2FkaiA8IDAuMDUpICU+JQogIGRwbHlyOjpmaWx0ZXIoYWJzKGF2Z19sb2dGQykgPiAwLjUpICU+JQogIHJvd25hbWVzKCkKCmxlbmd0aChmZWF0dXJlc19vaSkKYGBgCgpXZSBwcmVwYXJlIHRoZSBzY2FsZWQgZXhwcmVzc2lvbiBtYXRyaXggOgoKYGBge3IgbWF0X2NsdXN0ZXI0fQptYXRfZXhwcmVzc2lvbiA9IFNldXJhdDo6R2V0QXNzYXlEYXRhKHN1YnNvYmosIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiZGF0YSIpW2ZlYXR1cmVzX29pLCBdCm1hdF9leHByZXNzaW9uID0gTWF0cml4Ojp0KG1hdF9leHByZXNzaW9uKQptYXRfZXhwcmVzc2lvbiA9IGR5bnV0aWxzOjpzY2FsZV9xdWFudGlsZShtYXRfZXhwcmVzc2lvbikgIyBiZXR3ZWVuIDAgYW5kIDEKbWF0X2V4cHJlc3Npb24gPSBNYXRyaXg6OnQobWF0X2V4cHJlc3Npb24pCm1hdF9leHByZXNzaW9uID0gYXMubWF0cml4KG1hdF9leHByZXNzaW9uKSAjIG5vdCBzcGFyc2UKCmRpbShtYXRfZXhwcmVzc2lvbikKYGBgCgpXZSBwcmVwYXJlIHRoZSBoZWF0bWFwIGFubm90YXRpb24gOgoKYGBge3IgaGFfdG9wX2NsdXN0ZXI0fQpoYV90b3AgPSBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcEFubm90YXRpb24oCiAgY2x1c3Rlcl90eXBlID0gc3Vic29iaiRjbHVzdGVyX3R5cGUsCiAgc2FtcGxlX3R5cGUgPSBzdWJzb2JqJHNhbXBsZV90eXBlLAogIGNsdXN0ZXIgPSBzdWJzb2JqJHNldXJhdF9jbHVzdGVycywKICBjb2wgPSBsaXN0KGNsdXN0ZXJfdHlwZSA9IGNvbG9yX21hcmtlcnMsCiAgICAgICAgICAgICBzYW1wbGVfdHlwZSA9IHNldE5hbWVzKG5tID0gYygiSFMiLCAiSEQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiI0M1NUY0MCIsICIjMkM3OEU2IikpLAogICAgICAgICAgICAgY2x1c3RlciA9IHNldE5hbWVzKG5tID0gbGV2ZWxzKHN1YnNvYmokc2V1cmF0X2NsdXN0ZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcXVhcml1czo6Z2dfY29sb3JfaHVlKGxlbmd0aChsZXZlbHMoc3Vic29iaiRzZXVyYXRfY2x1c3RlcnMpKSkpKSkKYGBgCgpBbmQgdGhlIGhlYXRtYXAgOgoKYGBge3IgaGVhdG1hcF9jbHVzdGVyNCwgZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSAzMH0KaHQgPSBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChtYXRfZXhwcmVzc2lvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2wgPSBhcXVhcml1czo6Y29sb3JfY252LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQW5ub3RhdGlvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcF9hbm5vdGF0aW9uID0gaGFfdG9wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgR3JvdXBpbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fdGl0bGUgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbHVtbl9uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVmlzdWFsIGFzcGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfaGVhdG1hcF9sZWdlbmQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlciA9IFRSVUUpCgpDb21wbGV4SGVhdG1hcDo6ZHJhdyhodCwKICAgICAgICAgICAgICAgICAgICAgbWVyZ2VfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJib3R0b20iLAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2xlZ2VuZF9zaWRlID0gImJvdHRvbSIpCmBgYAoKCiMjIyBVcC1yZWd1bGF0ZWQgaW4gY2x1c3RlciA0CgpXZSBleHBsb3JlIGVucmljaG1lbnQgaW4gZ2VuZSBzZXRzIGZvciBjbHVzdGVyIDQuCgpgYGB7ciBvcmFfY2x1c3RlcjRfdXAsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNn0KZ2VuZXNfb2ZfaW50ZXJlc3QgPSByb3duYW1lcyhtYXJrX2NsdXN0ZXI0KQoKZW5yaWNocl9yZXN1bHRzID0gYXF1YXJpdXM6OnJ1bl9lbnJpY2hyKGdlbmVfbmFtZXMgPSBnZW5lc19vZl9pbnRlcmVzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVfY29ycmVzcCA9IGdlbmVfY29ycmVzcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVfc2V0cyA9IGdlbmVfc2V0c1ssIGMoImdzX25hbWUiLCAiZW5zZW1ibF9nZW5lIildLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFrZV9wbG90ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfdGl0bGUgPSAiVXAtcmVndWxhdGVkIGluIGNsdXN0ZXIgNCBjb21wYXJlZCB0byBvdGhlciBJRkUgYmFzYWwiKQoKbGlzdF9yZXN1bHRzW1tncm91cF9uYW1lXV0kZW5yaWNocl91cCA9IGVucmljaHJfcmVzdWx0cyRlZ28KCmVucmljaHJfcmVzdWx0cyRwbG90ICsKICBnZ3Bsb3QyOjp0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCmBgYAoKIyMjIERvd24tcmVndWxhdGVkIGluIGNsdXN0ZXIgNAoKV2UgZXhwbG9yZSBlbnJpY2htZW50IGluIGdlbmUgc2V0cyBmb3IgZ2VuZXMgZG93bnJlZ3VsYXRlZCBpbiBjbHVzdGVyIDQuCgpgYGB7ciBvcmFfY2x1c3RlcjRfZG4sIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNn0KZ2VuZXNfb2ZfaW50ZXJlc3QgPSByb3duYW1lcyhtYXJrX25vdDUpCgplbnJpY2hyX3Jlc3VsdHMgPSBhcXVhcml1czo6cnVuX2VucmljaHIoZ2VuZV9uYW1lcyA9IGdlbmVzX29mX2ludGVyZXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9jb3JyZXNwID0gZ2VuZV9jb3JyZXNwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWtlX3Bsb3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90aXRsZSA9ICJEb3duLXJlZ3VsYXRlZCBpbiBjbHVzdGVyIDQgY29tcGFyZWQgdG8gb3RoZXIgSUZFIGJhc2FsIikKCmxpc3RfcmVzdWx0c1tbZ3JvdXBfbmFtZV1dJGVucmljaHJfZG4gPSBlbnJpY2hyX3Jlc3VsdHMkZWdvCgplbnJpY2hyX3Jlc3VsdHMkcGxvdCArCiAgZ2dwbG90Mjo6dGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQpgYGAKCgojIyMgR1NFQQoKV2UgcnVuIGEgR1NFQSBmb3IgYWxsIGdlbmUgc2V0cywgZnJvbSB0aGUgZnVsbCBjb3VudCBtYXRyaXggOgoKYGBge3IgZ3NlYV9jbHVzdGVyNCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA0MH0KcmFua2VkX2dlbmVfbGlzdCA9IGFxdWFyaXVzOjpydW5fZm9sZGNoYW5nZShTZXVyYXQ6OkdldEFzc2F5RGF0YShzdWJzb2JqLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImNvdW50cyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwMSA9IGNvbG5hbWVzKHN1YnNvYmopW3N1YnNvYmpAYWN0aXZlLmlkZW50ID09IDVdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwMiA9IGNvbG5hbWVzKHN1YnNvYmopW3N1YnNvYmpAYWN0aXZlLmlkZW50ICE9IDVdKQpuYW1lcyhyYW5rZWRfZ2VuZV9saXN0KSA9IGdlbmVfY29ycmVzcCRJRAoKZ3NlYV9yZXN1bHRzID0gYXF1YXJpdXM6OmdzZWFfcnVuKHJhbmtlZF9nZW5lX2xpc3QgPSByYW5rZWRfZ2VuZV9saXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHU0VBX3BfdmFsX3RocmVzaCA9IDEpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRnc2VhID0gZ3NlYV9yZXN1bHRzCgpnc2VhX3Jlc3VsdHNAcmVzdWx0ICU+JQogIGRwbHlyOjpmaWx0ZXIocHZhbHVlIDwgMC4wNSkgJT4lCiAgZHBseXI6OnRvcF9uKC4sIG4gPSAyMDAsIHd0ID0gYWJzKE5FUykpICU+JQogIGRwbHlyOjptdXRhdGUodG9vX2xvbmcgPSBpZmVsc2UobmNoYXIoSUQpID4gNjAsIHllcyA9IFRSVUUsIG5vID0gRkFMU0UpKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gc3RyaW5ncjo6c3RyX3N1YihJRCwgZW5kID0gNjApKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gaWZlbHNlKHRvb19sb25nLCB5ZXMgPSBwYXN0ZTAoSUQsICIuLi4iKSwgbm8gPSBJRCkpICU+JQogIGFxdWFyaXVzOjpnc2VhX3Bsb3Qoc2hvd19sZWdlbmQgPSBUUlVFKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJHU0VBIHVzaW5nIGFsbCBnZW5lcyAoY291bnQgbWF0cml4KSIpICsKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpCmBgYAoKV2UgY29tcHV0ZSBhIGZvbGQgY2hhbmdlIHRhYmxlIHRvIG1ha2UgYSB3b3JkY2xvdWQgOgoKYGBge3IgZm9sZF9jaGFuZ2VfY2x1c3RlcjR9CmZvbGRfY2hhbmdlID0gZ2VuZV9jb3JyZXNwCmNvbG5hbWVzKGZvbGRfY2hhbmdlKSA9IGMoImdlbmVfbmFtZSIsICJJRCIpCnJvd25hbWVzKGZvbGRfY2hhbmdlKSA9IGZvbGRfY2hhbmdlJElECmZvbGRfY2hhbmdlJEZDID0gcmFua2VkX2dlbmVfbGlzdFtyb3duYW1lcyhmb2xkX2NoYW5nZSldCmZvbGRfY2hhbmdlJHBjdC4xID0gU2V1cmF0OjpHZXRBc3NheURhdGEoc3Vic29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJjb3VudHMiKVssIHN1YnNvYmpAYWN0aXZlLmlkZW50ID09ICI1Il0gJT4lCiAgYXBwbHkoLiwgTUFSR0lOID0gMSwgRlVOID0gZnVuY3Rpb24ob25lX3JvdykgewogICAgcmV0dXJuKCBtZWFuKG9uZV9yb3cgIT0gMCkgKQogIH0pCmZvbGRfY2hhbmdlJHBjdC4yID0gU2V1cmF0OjpHZXRBc3NheURhdGEoc3Vic29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJjb3VudHMiKVssIHN1YnNvYmpAYWN0aXZlLmlkZW50ICE9ICI1Il0gJT4lCiAgYXBwbHkoLiwgTUFSR0lOID0gMSwgRlVOID0gZnVuY3Rpb24ob25lX3JvdykgewogICAgcmV0dXJuKCBtZWFuKG9uZV9yb3cgIT0gMCkgKQogIH0pCmZvbGRfY2hhbmdlJEZDX3hfcGN0ID0gaWZlbHNlKGZvbGRfY2hhbmdlJEZDID4gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVzID0gZm9sZF9jaGFuZ2UkRkMgKiBmb2xkX2NoYW5nZSRwY3QuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm8gPSBmb2xkX2NoYW5nZSRGQyAqIGZvbGRfY2hhbmdlJHBjdC4yKQoKZGltKGZvbGRfY2hhbmdlKSA7IGhlYWQoZm9sZF9jaGFuZ2UpCmBgYAoKV2UgbWFrZSB0aGUgZ3NlYSBwbG90IGZvciBzb21lIGdlbmUgc2V0cyA6CgpgYGB7ciBnc2VhX3Bsb3RfY2x1c3RlcjQsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gMjB9CmdzX29pID0gYygiR09CUF9DRDRfUE9TSVRJVkVfQUxQSEFfQkVUQV9UX0NFTExfRElGRkVSRU5USUFUSU9OIiwKICAgICAgICAgICJHT0JQX1JFR1VMQVRJT05fT0ZfQUxQSEFfQkVUQV9UX0NFTExfRElGRkVSRU5USUFUSU9OIiwKICAgICAgICAgICJHT0JQX0FMUEhBX0JFVEFfVF9DRUxMX0RJRkZFUkVOVElBVElPTiIsCiAgICAgICAgICAiR09CUF9QT1NJVElWRV9SRUdVTEFUSU9OX09GX0FMUEhBX0JFVEFfVF9DRUxMX0FDVElWQVRJT04iLAogICAgICAgICAgIkdPQlBfQUxQSEFfQkVUQV9UX0NFTExfQUNUSVZBVElPTiIpCgpwbG90X2xpc3QgPSBtYWtlX2dzZWFfcGxvdChnc2VhX3Jlc3VsdHMsIGdzX29pLCBmb2xkX2NoYW5nZSwgIkZDX3hfcGN0IikKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSAyLCB3aWR0aHMgPSBjKDIsMS41KSkKYGBgCgojIyBPUlMgOiBIUyB2cyBIRAoKYGBge3IgZ3JvdXBfbmFtZV9pYmx9Cmdyb3VwX25hbWUgPSAiT1JTX0hTX3ZzX0hEIgpgYGAKCgpXZSBzdWJzZXQgdGhlIFNldXJhdCBvYmplY3QgYW5kIGNoYW5nZSBjZWxsIGlkZW50aXRpZXMgdG8gc2FtcGxlIHR5cGUuCgpgYGB7ciBzdWJfT1JTfQpzdWJzb2JqID0gc3Vic2V0KHNvYmosIGNsdXN0ZXJfdHlwZSA9PSAiT1JTIikKU2V1cmF0OjpJZGVudHMoc3Vic29iaikgPSBzdWJzb2JqJHNhbXBsZV90eXBlCgp0YWJsZShzdWJzb2JqJHNhbXBsZV90eXBlKQpgYGAKCiMjIyBERQoKV2UgaWRlbnRpZnkgREUgZ2VuZXMgYmV0d2VlbiBIUyBhbmQgSEQgOgoKYGBge3IgZGVfaWJsLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNX0KbWFyayA9IFNldXJhdDo6RmluZE1hcmtlcnMoc3Vic29iaiwgaWRlbnQuMSA9ICJIUyIsIGlkZW50LjIgPSAiSEQiKQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1tncm91cF9uYW1lXV0kbWFyayA9IG1hcmsKCmRpbShtYXJrKQpoZWFkKG1hcmssIG4gPSAyMCkKYGBgCgoKIyMjIFVwIGluIEhTCgpXZSBleHBsb3JlIGVucmljaG1lbnQgaW4gZ2VuZSBzZXRzIGZvciBIUyBwb3B1bGF0aW9uLgoKYGBge3Igb3JhX2libF9ocywgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA2fQpnZW5lc19vZl9pbnRlcmVzdCA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihhdmdfbG9nRkMgPiAwKSAlPiUKICByb3duYW1lcygpCgplbnJpY2hyX3Jlc3VsdHMgPSBhcXVhcml1czo6cnVuX2VucmljaHIoZ2VuZV9uYW1lcyA9IGdlbmVzX29mX2ludGVyZXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9jb3JyZXNwID0gZ2VuZV9jb3JyZXNwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWtlX3Bsb3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90aXRsZSA9ICJVcC1yZWd1bGF0ZWQgaW4gSFMgY29tcGFyZWQgdG8gSEQiKQoKbGlzdF9yZXN1bHRzW1tncm91cF9uYW1lXV0kZW5yaWNocl9ocyA9IGVucmljaHJfcmVzdWx0cyRlZ28KCmVucmljaHJfcmVzdWx0cyRwbG90ICsKICBnZ3Bsb3QyOjp0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCmBgYAoKIyMjIFVwIGluIEhECgpXZSBleHBsb3JlIGVucmljaG1lbnQgaW4gZ2VuZSBzZXRzIGZvciBIRCBwb3B1bGF0aW9uLgoKYGBge3Igb3JhX2libF9oZCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA2fQpnZW5lc19vZl9pbnRlcmVzdCA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihhdmdfbG9nRkMgPCAwKSAlPiUKICByb3duYW1lcygpCgplbnJpY2hyX3Jlc3VsdHMgPSBhcXVhcml1czo6cnVuX2VucmljaHIoZ2VuZV9uYW1lcyA9IGdlbmVzX29mX2ludGVyZXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9jb3JyZXNwID0gZ2VuZV9jb3JyZXNwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWtlX3Bsb3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF90aXRsZSA9ICJEb3duLXJlZ3VsYXRlZCBpbiBIUyBjb21wYXJlZCB0byBIRCIpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRlbnJpY2hyX2hkID0gZW5yaWNocl9yZXN1bHRzJGVnbwoKZW5yaWNocl9yZXN1bHRzJHBsb3QgKwogIGdncGxvdDI6OnRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkKYGBgCgoKIyMjIEdTRUEKCldlIHJ1biBhIEdTRUEgZm9yIGFsbCBnZW5lIHNldHMsIGZyb20gdGhlIGZ1bGwgY291bnQgbWF0cml4IDoKCmBgYHtyIGdzZWFfaW5faWJsLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDQwfQpyYW5rZWRfZ2VuZV9saXN0ID0gYXF1YXJpdXM6OnJ1bl9mb2xkY2hhbmdlKFNldXJhdDo6R2V0QXNzYXlEYXRhKHN1YnNvYmosIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiY291bnRzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAxID0gY29sbmFtZXMoc3Vic29iailbc3Vic29iakBhY3RpdmUuaWRlbnQgJWluJSAiSFMiXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cDIgPSBjb2xuYW1lcyhzdWJzb2JqKVtzdWJzb2JqQGFjdGl2ZS5pZGVudCAlaW4lICJIRCJdKQpuYW1lcyhyYW5rZWRfZ2VuZV9saXN0KSA9IGdlbmVfY29ycmVzcCRJRAoKZ3NlYV9yZXN1bHRzID0gYXF1YXJpdXM6OmdzZWFfcnVuKHJhbmtlZF9nZW5lX2xpc3QgPSByYW5rZWRfZ2VuZV9saXN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9zZXRzID0gZ2VuZV9zZXRzWywgYygiZ3NfbmFtZSIsICJlbnNlbWJsX2dlbmUiKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHU0VBX3BfdmFsX3RocmVzaCA9IDEpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRnc2VhID0gZ3NlYV9yZXN1bHRzCgpnc2VhX3Jlc3VsdHNAcmVzdWx0ICU+JQogIGRwbHlyOjpmaWx0ZXIocHZhbHVlIDwgMC4wNSkgJT4lCiAgZHBseXI6OnRvcF9uKC4sIG4gPSAyMDAsIHd0ID0gYWJzKE5FUykpICU+JQogIGRwbHlyOjptdXRhdGUodG9vX2xvbmcgPSBpZmVsc2UobmNoYXIoSUQpID4gNjAsIHllcyA9IFRSVUUsIG5vID0gRkFMU0UpKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gc3RyaW5ncjo6c3RyX3N1YihJRCwgZW5kID0gNjApKSAlPiUKICBkcGx5cjo6bXV0YXRlKElEID0gaWZlbHNlKHRvb19sb25nLCB5ZXMgPSBwYXN0ZTAoSUQsICIuLi4iKSwgbm8gPSBJRCkpICU+JQogIGFxdWFyaXVzOjpnc2VhX3Bsb3Qoc2hvd19sZWdlbmQgPSBUUlVFKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJHU0VBIHVzaW5nIGFsbCBnZW5lcyAoY291bnQgbWF0cml4KSIpICsKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCkpCmBgYAoKV2UgY29tcHV0ZSBhIGZvbGQgY2hhbmdlIHRhYmxlIHRvIG1ha2UgYSB3b3JkY2xvdWQgOgoKYGBge3IgZm9sZF9jaGFuZ2VfaWJsfQpmb2xkX2NoYW5nZSA9IGdlbmVfY29ycmVzcApjb2xuYW1lcyhmb2xkX2NoYW5nZSkgPSBjKCJnZW5lX25hbWUiLCAiSUQiKQpyb3duYW1lcyhmb2xkX2NoYW5nZSkgPSBmb2xkX2NoYW5nZSRJRApmb2xkX2NoYW5nZSRGQyA9IHJhbmtlZF9nZW5lX2xpc3Rbcm93bmFtZXMoZm9sZF9jaGFuZ2UpXQpmb2xkX2NoYW5nZSRwY3QuMSA9IFNldXJhdDo6R2V0QXNzYXlEYXRhKHN1YnNvYmosIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiY291bnRzIilbLCBzdWJzb2JqQGFjdGl2ZS5pZGVudCA9PSAiSFMiXSAlPiUKICBhcHBseSguLCBNQVJHSU4gPSAxLCBGVU4gPSBmdW5jdGlvbihvbmVfcm93KSB7CiAgICByZXR1cm4oIG1lYW4ob25lX3JvdyAhPSAwKSApCiAgfSkKZm9sZF9jaGFuZ2UkcGN0LjIgPSBTZXVyYXQ6OkdldEFzc2F5RGF0YShzdWJzb2JqLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImNvdW50cyIpWywgc3Vic29iakBhY3RpdmUuaWRlbnQgPT0gIkhEIl0gJT4lCiAgYXBwbHkoLiwgTUFSR0lOID0gMSwgRlVOID0gZnVuY3Rpb24ob25lX3JvdykgewogICAgcmV0dXJuKCBtZWFuKG9uZV9yb3cgIT0gMCkgKQogIH0pCmZvbGRfY2hhbmdlJEZDX3hfcGN0ID0gaWZlbHNlKGZvbGRfY2hhbmdlJEZDID4gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVzID0gZm9sZF9jaGFuZ2UkRkMgKiBmb2xkX2NoYW5nZSRwY3QuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm8gPSBmb2xkX2NoYW5nZSRGQyAqIGZvbGRfY2hhbmdlJHBjdC4yKQoKZGltKGZvbGRfY2hhbmdlKSA7IGhlYWQoZm9sZF9jaGFuZ2UpCmBgYAoKV2UgbWFrZSB0aGUgZ3NlYSBwbG90IGZvciBzb21lIGdlbmUgc2V0cyA6CgpgYGB7ciBnc2VhX3Bsb3Rfd2l0aGluX2libCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSAyNX0KZ3Nfb2kgPSBjKCJSRUFDVE9NRV9FWFBPUlRfT0ZfVklSQUxfUklCT05VQ0xFT1BST1RFSU5TX0ZST01fTlVDTEVVUyIsCiAgICAgICAgICAiR09CUF9NSUNST1RVQlVMRV9BTkNIT1JJTkciLAogICAgICAgICAgIkdPQlBfSElTVE9ORV9IM19LNF9UUklNRVRIWUxBVElPTiIsCiAgICAgICAgICAiR09CUF9ISVNUT05FX0gzX0s0X01PTk9NRVRIWUxBVElPTiIsCiAgICAgICAgICAiR09CUF9NSVRPUEhBR1kiLAogICAgICAgICAgIlJFQUNUT01FX1NFUk9UT05JTl9ORVVST1RSQU5TTUlUVEVSX1JFTEVBU0VfQ1lDTEUiLAogICAgICAgICAgIlJFQUNUT01FX0RPUEFNSU5FX05FVVJPVFJBTlNNSVRURVJfUkVMRUFTRV9DWUNMRSIpCgpwbG90X2xpc3QgPSBtYWtlX2dzZWFfcGxvdChnc2VhX3Jlc3VsdHMsIGdzX29pLCBmb2xkX2NoYW5nZSwgIkZDX3hfcGN0IikKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSAyLCB3aWR0aHMgPSBjKDIsMS41KSkKYGBgCgojIyBJRkUgYmFzYWwgOiBIUyB2cyBIRAoKYGBge3IgZ3JvdXBfbmFtZV9vcnN9Cmdyb3VwX25hbWUgPSAiSUZFIGJhc2FsX0hTX3ZzX0hEIgpgYGAKCldlIHN1YnNldCB0aGUgU2V1cmF0IG9iamVjdCBhbmQgY2hhbmdlIGNlbGwgaWRlbnRpdGllcyB0byBzYW1wbGUgdHlwZS4KCmBgYHtyIHN1Yl9JRkUgYmFzYWx9CnN1YnNvYmogPSBzdWJzZXQoc29iaiwgY2x1c3Rlcl90eXBlID09ICJJRkUgYmFzYWwiKQpTZXVyYXQ6OklkZW50cyhzdWJzb2JqKSA9IHN1YnNvYmokc2FtcGxlX3R5cGUKCnRhYmxlKHN1YnNvYmokc2FtcGxlX3R5cGUpCmBgYAoKIyMjIERFCgpXZSBpZGVudGlmeSBERSBnZW5lcyBiZXR3ZWVuIEhTIGFuZCBIRCA6CgpgYGB7ciBkZV9vcnMsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSA1fQptYXJrID0gU2V1cmF0OjpGaW5kTWFya2VycyhzdWJzb2JqLCBpZGVudC4xID0gIkhTIiwgaWRlbnQuMiA9ICJIRCIpCgptYXJrID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKHBfdmFsX2FkaiA8IDAuMDUpICU+JQogIGRwbHlyOjphcnJhbmdlKC1hdmdfbG9nRkMsIHBjdC4xIC0gcGN0LjIpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRtYXJrID0gbWFyawoKZGltKG1hcmspCmhlYWQobWFyaywgbiA9IDIwKQpgYGAKCgojIyMgVXAgaW4gSFMKCldlIGV4cGxvcmUgZW5yaWNobWVudCBpbiBnZW5lIHNldHMgZm9yIEhTIHBvcHVsYXRpb24uCgpgYGB7ciBvcmFfb3JzX2hzLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDZ9CmdlbmVzX29mX2ludGVyZXN0ID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKGF2Z19sb2dGQyA+IDApICU+JQogIHJvd25hbWVzKCkKCmVucmljaHJfcmVzdWx0cyA9IGFxdWFyaXVzOjpydW5fZW5yaWNocihnZW5lX25hbWVzID0gZ2VuZXNfb2ZfaW50ZXJlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2NvcnJlc3AgPSBnZW5lX2NvcnJlc3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3NldHMgPSBnZW5lX3NldHNbLCBjKCJnc19uYW1lIiwgImVuc2VtYmxfZ2VuZSIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ha2VfcGxvdCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIlVwLXJlZ3VsYXRlZCBpbiBIUyBjb21wYXJlZCB0byBIRCIpCgpsaXN0X3Jlc3VsdHNbW2dyb3VwX25hbWVdXSRlbnJpY2hyX2hzID0gZW5yaWNocl9yZXN1bHRzJGVnbwoKZW5yaWNocl9yZXN1bHRzJHBsb3QgKwogIGdncGxvdDI6OnRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkKYGBgCgojIyMgVXAgaW4gSEQKCldlIGV4cGxvcmUgZW5yaWNobWVudCBpbiBnZW5lIHNldHMgZm9yIEhEIHBvcHVsYXRpb24uCgpgYGB7ciBvcmFfb3JzX2hkLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDZ9CmdlbmVzX29mX2ludGVyZXN0ID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKGF2Z19sb2dGQyA8IDApICU+JQogIHJvd25hbWVzKCkKCmVucmljaHJfcmVzdWx0cyA9IGFxdWFyaXVzOjpydW5fZW5yaWNocihnZW5lX25hbWVzID0gZ2VuZXNfb2ZfaW50ZXJlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2NvcnJlc3AgPSBnZW5lX2NvcnJlc3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3NldHMgPSBnZW5lX3NldHNbLCBjKCJnc19uYW1lIiwgImVuc2VtYmxfZ2VuZSIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ha2VfcGxvdCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIkRvd24tcmVndWxhdGVkIGluIEhTIGNvbXBhcmVkIHRvIEhEIikKCmxpc3RfcmVzdWx0c1tbZ3JvdXBfbmFtZV1dJGVucmljaHJfaGQgPSBlbnJpY2hyX3Jlc3VsdHMkZWdvCgplbnJpY2hyX3Jlc3VsdHMkcGxvdCArCiAgZ2dwbG90Mjo6dGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQpgYGAKCgojIyMgR1NFQQoKV2UgcnVuIGEgR1NFQSBmb3IgYWxsIGdlbmUgc2V0cywgZnJvbSB0aGUgZnVsbCBjb3VudCBtYXRyaXggOgoKYGBge3IgZ3NlYV9pbl9vcnMsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNDB9CnJhbmtlZF9nZW5lX2xpc3QgPSBhcXVhcml1czo6cnVuX2ZvbGRjaGFuZ2UoU2V1cmF0OjpHZXRBc3NheURhdGEoc3Vic29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJjb3VudHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cDEgPSBjb2xuYW1lcyhzdWJzb2JqKVtzdWJzb2JqQGFjdGl2ZS5pZGVudCAlaW4lICJIUyJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwMiA9IGNvbG5hbWVzKHN1YnNvYmopW3N1YnNvYmpAYWN0aXZlLmlkZW50ICVpbiUgIkhEIl0pCm5hbWVzKHJhbmtlZF9nZW5lX2xpc3QpID0gZ2VuZV9jb3JyZXNwJElECgpnc2VhX3Jlc3VsdHMgPSBhcXVhcml1czo6Z3NlYV9ydW4ocmFua2VkX2dlbmVfbGlzdCA9IHJhbmtlZF9nZW5lX2xpc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX3NldHMgPSBnZW5lX3NldHNbLCBjKCJnc19uYW1lIiwgImVuc2VtYmxfZ2VuZSIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdTRUFfcF92YWxfdGhyZXNoID0gMSkKCmxpc3RfcmVzdWx0c1tbZ3JvdXBfbmFtZV1dJGdzZWEgPSBnc2VhX3Jlc3VsdHMKCmdzZWFfcmVzdWx0c0ByZXN1bHQgJT4lCiAgZHBseXI6OmZpbHRlcihwdmFsdWUgPCAwLjA1KSAlPiUKICBkcGx5cjo6dG9wX24oLiwgbiA9IDIwMCwgd3QgPSBhYnMoTkVTKSkgJT4lCiAgZHBseXI6Om11dGF0ZSh0b29fbG9uZyA9IGlmZWxzZShuY2hhcihJRCkgPiA2MCwgeWVzID0gVFJVRSwgbm8gPSBGQUxTRSkpICU+JQogIGRwbHlyOjptdXRhdGUoSUQgPSBzdHJpbmdyOjpzdHJfc3ViKElELCBlbmQgPSA2MCkpICU+JQogIGRwbHlyOjptdXRhdGUoSUQgPSBpZmVsc2UodG9vX2xvbmcsIHllcyA9IHBhc3RlMChJRCwgIi4uLiIpLCBubyA9IElEKSkgJT4lCiAgYXF1YXJpdXM6OmdzZWFfcGxvdChzaG93X2xlZ2VuZCA9IFRSVUUpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkdTRUEgdXNpbmcgYWxsIGdlbmVzIChjb3VudCBtYXRyaXgpIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkKYGBgCgpXZSBjb21wdXRlIGEgZm9sZCBjaGFuZ2UgdGFibGUgdG8gbWFrZSBhIHdvcmRjbG91ZCA6CgpgYGB7ciBmb2xkX2NoYW5nZV9vcnN9CmZvbGRfY2hhbmdlID0gZ2VuZV9jb3JyZXNwCmNvbG5hbWVzKGZvbGRfY2hhbmdlKSA9IGMoImdlbmVfbmFtZSIsICJJRCIpCnJvd25hbWVzKGZvbGRfY2hhbmdlKSA9IGZvbGRfY2hhbmdlJElECmZvbGRfY2hhbmdlJEZDID0gcmFua2VkX2dlbmVfbGlzdFtyb3duYW1lcyhmb2xkX2NoYW5nZSldCmZvbGRfY2hhbmdlJHBjdC4xID0gU2V1cmF0OjpHZXRBc3NheURhdGEoc3Vic29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJjb3VudHMiKVssIHN1YnNvYmpAYWN0aXZlLmlkZW50ID09ICJIUyJdICU+JQogIGFwcGx5KC4sIE1BUkdJTiA9IDEsIEZVTiA9IGZ1bmN0aW9uKG9uZV9yb3cpIHsKICAgIHJldHVybiggbWVhbihvbmVfcm93ICE9IDApICkKICB9KQpmb2xkX2NoYW5nZSRwY3QuMiA9IFNldXJhdDo6R2V0QXNzYXlEYXRhKHN1YnNvYmosIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiY291bnRzIilbLCBzdWJzb2JqQGFjdGl2ZS5pZGVudCA9PSAiSEQiXSAlPiUKICBhcHBseSguLCBNQVJHSU4gPSAxLCBGVU4gPSBmdW5jdGlvbihvbmVfcm93KSB7CiAgICByZXR1cm4oIG1lYW4ob25lX3JvdyAhPSAwKSApCiAgfSkKZm9sZF9jaGFuZ2UkRkNfeF9wY3QgPSBpZmVsc2UoZm9sZF9jaGFuZ2UkRkMgPiAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZXMgPSBmb2xkX2NoYW5nZSRGQyAqIGZvbGRfY2hhbmdlJHBjdC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBubyA9IGZvbGRfY2hhbmdlJEZDICogZm9sZF9jaGFuZ2UkcGN0LjIpCgpkaW0oZm9sZF9jaGFuZ2UpIDsgaGVhZChmb2xkX2NoYW5nZSkKYGBgCgpXZSBtYWtlIHRoZSBnc2VhIHBsb3QgZm9yIHNvbWUgZ2VuZSBzZXRzIDoKCmBgYHtyIGdzZWFfcGxvdF93aXRoaW5fb3JzLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDM1fQpnc19vaSA9IGMoIkdPQlBfUkVTUE9OU0VfVE9fTU9MRUNVTEVfT0ZfQkFDVEVSSUFMX09SSUdJTiIsCiAgICAgICAgICAiR09CUF9IVU1PUkFMX0lNTVVORV9SRVNQT05TRSIsCiAgICAgICAgICAiSEFMTE1BUktfQVBPUFRPU0lTIiwKICAgICAgICAgICJHT0JQX0dSQU5VTE9DWVRFX0NIRU1PVEFYSVMiLAogICAgICAgICAgIkdPQlBfR1JBTlVMT0NZVEVfTUlHUkFUSU9OIiwKICAgICAgICAgICJIQUxMTUFSS19JTlRFUkZFUk9OX0FMUEhBX1JFU1BPTlNFIiwKICAgICAgICAgICJIQUxMTUFSS19JTlRFUkZFUk9OX0dBTU1BX1JFU1BPTlNFIiwKICAgICAgICAgICJIQUxMTUFSS19UTkZBX1NJR05BTElOR19WSUFfTkZLQiIsCiAgICAgICAgICAiSEFMTE1BUktfSFlQT1hJQSIsCiAgICAgICAgICAiR09CUF9SRVNQT05TRV9UT19UUkFOU0ZPUk1JTkdfR1JPV1RIX0ZBQ1RPUl9CRVRBIikKCnBsb3RfbGlzdCA9IG1ha2VfZ3NlYV9wbG90KGdzZWFfcmVzdWx0cywgZ3Nfb2ksIGZvbGRfY2hhbmdlLCAiRkNfeF9wY3QiKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDIsIHdpZHRocyA9IGMoMiwxLjUpKQpgYGAKCiMgR2xvYmFsIGhlYXRtYXAKCldlIHJlcHJlc2VudCBhbGwgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIG9uIGEgaGVhdG1hcC4gRmlyc3QsIHdlIGV4dHJhY3QgYWxsIERFIGdlbmVzIDoKCmBgYHtyIHByZXBfZ2VuZXN9CmZlYXR1cmVzX29pID0gYyhtYXJrX3RvX2xhYmVsJGdlbmVfbmFtZSwKICAgICAgICAgICAgICAgIHJvd25hbWVzKGxpc3RfcmVzdWx0cyRPUlNfSFNfdnNfSEQkbWFyayksCiAgICAgICAgICAgICAgICByb3duYW1lcyhsaXN0X3Jlc3VsdHMkSUZFX2Jhc2FsX0hTX3ZzX0hEJG1hcmspKSAlPiUKICB1bmlxdWUoKQoKbGVuZ3RoKGZlYXR1cmVzX29pKQpgYGAKCldlIHByZXBhcmUgdGhlIHNjYWxlZCBleHByZXNzaW9uIG1hdHJpeCA6CgpgYGB7ciBtYXR9Cm1hdF9leHByZXNzaW9uID0gU2V1cmF0OjpHZXRBc3NheURhdGEoc29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJkYXRhIilbZmVhdHVyZXNfb2ksIF0KbWF0X2V4cHJlc3Npb24gPSBNYXRyaXg6OnQobWF0X2V4cHJlc3Npb24pCm1hdF9leHByZXNzaW9uID0gZHludXRpbHM6OnNjYWxlX3F1YW50aWxlKG1hdF9leHByZXNzaW9uKSAjIGJldHdlZW4gMCBhbmQgMQptYXRfZXhwcmVzc2lvbiA9IE1hdHJpeDo6dChtYXRfZXhwcmVzc2lvbikKbWF0X2V4cHJlc3Npb24gPSBhcy5tYXRyaXgobWF0X2V4cHJlc3Npb24pICMgbm90IHNwYXJzZQoKZGltKG1hdF9leHByZXNzaW9uKQpgYGAKCldlIHByZXBhcmUgdGhlIGhlYXRtYXAgYW5ub3RhdGlvbiA6CgpgYGB7ciBoYV90b3B9CmhhX3RvcCA9IENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbigKICBjbHVzdGVyX3R5cGUgPSBzb2JqJGNsdXN0ZXJfdHlwZSwKICBzYW1wbGVfdHlwZSA9IHNvYmokc2FtcGxlX3R5cGUsCiAgY2x1c3RlciA9IHNvYmokc2V1cmF0X2NsdXN0ZXJzLAogIGNvbCA9IGxpc3QoY2x1c3Rlcl90eXBlID0gY29sb3JfbWFya2VycywKICAgICAgICAgICAgIHNhbXBsZV90eXBlID0gc2V0TmFtZXMobm0gPSBjKCJIUyIsICJIRCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCIjQzU1RjQwIiwgIiMyQzc4RTYiKSksCiAgICAgICAgICAgICBjbHVzdGVyID0gc2V0TmFtZXMobm0gPSBsZXZlbHMoc29iaiRzZXVyYXRfY2x1c3RlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFxdWFyaXVzOjpnZ19jb2xvcl9odWUobGVuZ3RoKGxldmVscyhzb2JqJHNldXJhdF9jbHVzdGVycykpKSkpKQpgYGAKCkFuZCB0aGUgaGVhdG1hcCA6CgpgYGB7ciBoZWF0bWFwLCBmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDQ1fQpzb2JqJGNlbGxfZ3JvdXAgPSBwYXN0ZTAoc29iaiRjbHVzdGVyX3R5cGUsIHNvYmokc2FtcGxlX3R5cGUpICU+JQogIGFzLmZhY3RvcigpCgpodCA9IENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdF9leHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbCA9IGFxdWFyaXVzOjpjb2xvcl9jbnYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBbm5vdGF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wX2Fubm90YXRpb24gPSBoYV90b3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBHcm91cGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9vcmRlciA9IHNvYmpAbWV0YS5kYXRhICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmFycmFuZ2UoY2x1c3Rlcl90eXBlLCBzYW1wbGVfdHlwZSwgc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd25hbWVzKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3NwbGl0ID0gc29iaiRjZWxsX2dyb3VwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9nYXAgPSB1bml0KGMoMC4wMSwgMiwgMC4wMSksICJtbSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl90aXRsZSA9IE5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbHVtbl9uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVmlzdWFsIGFzcGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfaGVhdG1hcF9sZWdlbmQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlciA9IFRSVUUpCgpDb21wbGV4SGVhdG1hcDo6ZHJhdyhodCwKICAgICAgICAgICAgICAgICAgICAgbWVyZ2VfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJib3R0b20iLAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2xlZ2VuZF9zaWRlID0gImJvdHRvbSIpCmBgYAoKIyBTYXZlCgpXZSBzYXZlIHRoZSBsaXN0IG9mIHJlc3VsdHMgOgoKYGBge3Igc2F2ZV9saXN0X3Jlc3VsdHN9CnNhdmVSRFMobGlzdF9yZXN1bHRzLCBmaWxlID0gcGFzdGUwKG91dF9kaXIsICIvIiwgc2F2ZV9uYW1lLCAiX2xpc3RfcmVzdWx0cy5yZHMiKSkKYGBgCgoKIyBSIFNlc3Npb24KCmBgYHtyIHNlc3Npb25pbmZvLCBlY2hvID0gRkFMU0UsIGZvbGRfb3V0cHV0ID0gVFJVRX0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==